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

     1  // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved.
     2  //
     3  // Licensed under the MIT License
     4  //
     5  //
     6  // Permission is hereby granted, free of charge, to any person obtaining a copy
     7  // of this software and associated documentation files (the "Software"), to deal
     8  // in the Software without restriction, including without limitation the rights
     9  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    10  // copies of the Software, and to permit persons to whom the Software is
    11  // furnished to do so, subject to the following conditions:
    12  //
    13  // The above copyright notice and this permission notice shall be included in all
    14  // copies or substantial portions of the Software.
    15  //
    16  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    17  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    18  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    19  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    20  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    21  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    22  // SOFTWARE.
    23  
    24  package taskrun
    25  
    26  import (
    27  	"fmt"
    28  	"os"
    29  	"strconv"
    30  
    31  	"github.com/sirupsen/logrus"
    32  	"github.com/spf13/cobra"
    33  	"golang.org/x/term"
    34  
    35  	"github.com/swaros/contxt/module/configure"
    36  	"github.com/swaros/contxt/module/dirhandle"
    37  	"github.com/swaros/contxt/module/systools"
    38  	"github.com/swaros/manout"
    39  )
    40  
    41  var (
    42  	log = &logrus.Logger{
    43  		Out:       os.Stdout,
    44  		Formatter: new(logrus.TextFormatter),
    45  		Hooks:     make(logrus.LevelHooks),
    46  		Level:     logrus.ErrorLevel,
    47  	}
    48  
    49  	// cobra stuff
    50  	showColors    bool
    51  	loglevel      string
    52  	pathIndex     int
    53  	deleteWs      string
    54  	clearTask     bool
    55  	setWs         string
    56  	runAtAll      bool
    57  	leftLen       int
    58  	rightLen      int
    59  	yamlIndent    int
    60  	showInvTarget bool
    61  	uselastIndex  bool
    62  	showHints     bool
    63  	preVars       map[string]string
    64  
    65  	rootCmd = &cobra.Command{
    66  		Use:   "contxt",
    67  		Short: "workspaces for the shell",
    68  		Long: `Contxt helps you to organize projects.
    69  it helps also to execute tasks depending these projects.
    70  this task can be used to setup and cleanup the workspace
    71  if you enter or leave them.`,
    72  		Run: func(cmd *cobra.Command, args []string) {
    73  			checkDefaultFlags(cmd, args)
    74  
    75  		},
    76  	}
    77  
    78  	completionCmd = &cobra.Command{
    79  		Use:   "completion [bash|zsh|fish|powershell]",
    80  		Short: "Generate completion script",
    81  		Long: `To load completions:
    82  
    83  Bash:
    84  
    85    $ source <(contxt completion bash)
    86  
    87    # To load completions for each session, execute once:
    88    # Linux:
    89    $ contxt completion bash > /etc/bash_completion.d/contxt
    90    # macOS:
    91    $ contxt completion bash > /usr/local/etc/bash_completion.d/contxt
    92  
    93  Zsh:
    94  
    95    # If shell completion is not already enabled in your environment,
    96    # you will need to enable it.  You can execute the following once:
    97  
    98    $ echo "autoload -U compinit; compinit" >> ~/.zshrc
    99  
   100    # To load completions for each session, execute once:
   101    $ contxt completion zsh > "${fpath[1]}/_contxt"
   102  
   103    # You will need to start a new shell for this setup to take effect.
   104  
   105  fish:
   106  
   107    $ contxt completion fish | source
   108  
   109    # To load completions for each session, execute once:
   110    $ contxt completion fish > ~/.config/fish/completions/contxt.fish
   111  PowerShell:
   112  
   113    PS> contxt completion powershell | Out-String | Invoke-Expression
   114  
   115    # To load completions for every new session, run:
   116    PS> contxt completion powershell > contxt.ps1
   117    # and source this file from your PowerShell profile.
   118  
   119    `,
   120  		DisableFlagsInUseLine: true,
   121  		ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
   122  		Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
   123  		Run: func(cmd *cobra.Command, args []string) {
   124  			switch args[0] {
   125  			case "bash":
   126  				cmd.Root().GenBashCompletion(os.Stdout)
   127  			case "zsh":
   128  				cmd.Root().GenZshCompletion(os.Stdout)
   129  			case "fish":
   130  				cmd.Root().GenFishCompletion(os.Stdout, true)
   131  			case "powershell":
   132  				cmd.Root().GenPowerShellCompletion(os.Stdout)
   133  			}
   134  		},
   135  	}
   136  
   137  	gotoCmd = &cobra.Command{
   138  		Use:   "switch",
   139  		Short: "switch workspace",
   140  		Long: `switch the workspace to a existing ones.
   141  all defined onEnter and onLeave task will be executed
   142  if these task are defined
   143  `,
   144  		Run: func(_ *cobra.Command, args []string) {
   145  			FindWorkspaceInfoByTemplate(nil)
   146  			if len(args) > 0 {
   147  				for _, arg := range args {
   148  					doMagicParamOne(arg)
   149  				}
   150  			}
   151  		},
   152  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   153  			if len(args) != 0 {
   154  				return nil, cobra.ShellCompDirectiveNoFileComp
   155  			}
   156  			targets := configure.GetGlobalConfig().ListWorkSpaces()
   157  			return targets, cobra.ShellCompDirectiveNoFileComp
   158  		},
   159  	}
   160  
   161  	workspaceCmd = &cobra.Command{
   162  		Use:   "workspace",
   163  		Short: "manage workspaces",
   164  		Long: `create a new workspace 'ctx workspace new <name>'. 
   165  Remove a workspace 'ctx workspace rm <name>'.
   166  list all workspaces 'ctx workspace list'.
   167  scan for new projects in the workspace 'ctx workspace scan'`,
   168  	}
   169  
   170  	wsNewCmd = &cobra.Command{
   171  		Use:   "new",
   172  		Short: "create a new workspace",
   173  		Long: `
   174  create a new workspace.
   175  this will trigger any onLeave task defined in the workspace
   176  and also onEnter task defined in the new workspace
   177  `,
   178  		Run: func(cmd *cobra.Command, args []string) {
   179  			checkDefaultFlags(cmd, args)
   180  			if len(args) > 0 {
   181  				if err := configure.GetGlobalConfig().AddWorkSpace(args[0], CallBackOldWs, CallBackNewWs); err != nil {
   182  					fmt.Println(err)
   183  				} else {
   184  					CtxOut("workspace created ", args[0])
   185  				}
   186  
   187  			} else {
   188  				fmt.Println("no workspace name given")
   189  			}
   190  		},
   191  	}
   192  
   193  	wsRmCmd = &cobra.Command{
   194  		Use:   "rm",
   195  		Short: "remove a workspace by given name",
   196  		Long: `
   197  remove a workspace.
   198  this will trigger any onLeave task defined in the workspace
   199  and also onEnter task defined in the new workspace
   200  `,
   201  		Run: func(cmd *cobra.Command, args []string) {
   202  			checkDefaultFlags(cmd, args)
   203  			if len(args) > 0 {
   204  				if err := configure.GetGlobalConfig().RemoveWorkspace(args[0]); err != nil {
   205  					manout.Error("error while trying to remove workspace", err)
   206  					systools.Exit(systools.ErrorBySystem)
   207  				} else {
   208  					if err := configure.GetGlobalConfig().SaveConfiguration(); err != nil {
   209  						manout.Error("error while trying to save configuration", err)
   210  						systools.Exit(systools.ErrorBySystem)
   211  					}
   212  					CtxOut("workspace removed ", args[0])
   213  				}
   214  			} else {
   215  				fmt.Println("no workspace name given")
   216  			}
   217  		},
   218  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   219  			if len(args) != 0 {
   220  				return nil, cobra.ShellCompDirectiveNoFileComp
   221  			}
   222  			targets := configure.GetGlobalConfig().ListWorkSpaces()
   223  			return targets, cobra.ShellCompDirectiveNoFileComp
   224  		},
   225  	}
   226  
   227  	wsListCmd = &cobra.Command{
   228  		Use:   "list",
   229  		Short: "list all workspaces",
   230  		Long:  "list all workspaces",
   231  		Run: func(cmd *cobra.Command, args []string) {
   232  			checkDefaultFlags(cmd, args)
   233  			workspacesList := configure.GetGlobalConfig().ListWorkSpaces()
   234  			for _, ws := range workspacesList {
   235  				fmt.Println(ws)
   236  			}
   237  		},
   238  	}
   239  
   240  	wsScanCmd = &cobra.Command{
   241  		Use:   "scan",
   242  		Short: "scan for new projects in the workspace",
   243  		Long:  "scan for new projects in the workspace",
   244  		Run: func(cmd *cobra.Command, args []string) {
   245  			checkDefaultFlags(cmd, args)
   246  			all, updated := FindWorkspaceInfoByTemplate(func(ws string, cnt int, update bool, info configure.WorkspaceInfoV2) {
   247  				if update {
   248  					CtxOut(manout.ForeBlue, ws, " ", manout.ForeDarkGrey, " ", info.Path, manout.ForeGreen, "\tupdated")
   249  				} else {
   250  					CtxOut(manout.ForeBlue, ws, " ", manout.ForeDarkGrey, " ", info.Path, manout.ForeYellow, "\tignored. nothing to do.")
   251  				}
   252  			})
   253  			CtxOut("found ", all, " projects and updated ", updated, " projects")
   254  
   255  		},
   256  	}
   257  
   258  	wsUseCmd = &cobra.Command{
   259  		Use:   "use",
   260  		Short: "use a workspace",
   261  		Long: `use a workspace. this is then the new active workspace
   262  this will trigger any onLeave task defined in the workspace
   263  and also onEnter task defined in the new workspace
   264  `,
   265  		Run: func(cmd *cobra.Command, args []string) {
   266  			checkDefaultFlags(cmd, args)
   267  			if len(args) > 0 {
   268  				if err := configure.GetGlobalConfig().ChangeWorkspace(args[0], CallBackOldWs, CallBackNewWs); err != nil {
   269  					fmt.Println(err)
   270  				} else {
   271  					CtxOut("workspace used ", args[0])
   272  				}
   273  			} else {
   274  				fmt.Println("no workspace name given")
   275  			}
   276  		},
   277  	}
   278  	dirCmd = &cobra.Command{
   279  		Use:   "dir",
   280  		Short: "handle workspaces and assigned paths",
   281  		Long:  "manage workspaces and paths they are assigned",
   282  		Run: func(cmd *cobra.Command, args []string) {
   283  			checkDefaultFlags(cmd, args)
   284  			checkDirFlags(cmd, args)
   285  			defaulttask := true
   286  			if pathIndex >= 0 {
   287  				pathStr := configure.GetGlobalConfig().GetPathByIndex(strconv.Itoa(pathIndex), ".")
   288  				fmt.Println(pathStr)
   289  				defaulttask = false
   290  			}
   291  
   292  			if uselastIndex {
   293  				GetLogger().WithField("dirIndex", configure.GetGlobalConfig().UsedV2Config.CurrentSet).Debug("current stored index")
   294  				pathStr := configure.GetGlobalConfig().GetActivePath(".")
   295  				fmt.Println(pathStr)
   296  				defaulttask = false
   297  			}
   298  
   299  			if clearTask {
   300  				GetLogger().Info("got clear command")
   301  				configure.GetGlobalConfig().ClearPaths()
   302  				defaulttask = false
   303  			}
   304  
   305  			if deleteWs != "" {
   306  				GetLogger().WithField("workspace", deleteWs).Info("got remove workspace option")
   307  				if err := configure.GetGlobalConfig().RemoveWorkspace(deleteWs); err != nil {
   308  					manout.Error("error while trying to deleting workspace", err)
   309  					systools.Exit(systools.ErrorBySystem)
   310  				}
   311  				if err := configure.GetGlobalConfig().SaveConfiguration(); err != nil {
   312  					manout.Error("error while trying to save configuration", err)
   313  					systools.Exit(systools.ErrorBySystem)
   314  				}
   315  				defaulttask = false
   316  			}
   317  
   318  			if setWs != "" {
   319  				GetLogger().WithField("workspace", setWs).Info("create a new worspace")
   320  				configure.GetGlobalConfig().ChangeWorkspace(setWs, CallBackOldWs, CallBackNewWs)
   321  				defaulttask = false
   322  			}
   323  
   324  			if defaulttask {
   325  				printInfo()
   326  			}
   327  		},
   328  	}
   329  
   330  	showPaths = &cobra.Command{
   331  		Use:   "paths",
   332  		Short: "show assigned paths",
   333  		Run: func(cmd *cobra.Command, args []string) {
   334  			checkDefaultFlags(cmd, args)
   335  			PrintCnPaths()
   336  		},
   337  	}
   338  
   339  	findPath = &cobra.Command{
   340  		Use:   "find",
   341  		Short: "find path by a part of them",
   342  		Run: func(cmd *cobra.Command, args []string) {
   343  			checkDefaultFlags(cmd, args)
   344  			if len(args) < 1 {
   345  				pathStr := configure.GetGlobalConfig().GetActivePath(".")
   346  				fmt.Println(pathStr)
   347  			} else {
   348  				path, _ := DirFindApplyAndSave(args)
   349  				fmt.Println(path) // path only as output. so cn can handle it
   350  			}
   351  		},
   352  	}
   353  
   354  	listPaths = &cobra.Command{
   355  		Use:   "list",
   356  		Short: "show assigned paths",
   357  		Run: func(cmd *cobra.Command, args []string) {
   358  			checkDefaultFlags(cmd, args)
   359  
   360  			for _, p := range configure.GetGlobalConfig().ListWorkSpaces() {
   361  				fmt.Println(p)
   362  			}
   363  		},
   364  	}
   365  
   366  	addPaths = &cobra.Command{
   367  		Use:   "add",
   368  		Short: "add current path (pwd) to the current workspace",
   369  		Run: func(cmd *cobra.Command, args []string) {
   370  			checkDefaultFlags(cmd, args)
   371  			dir, err := dirhandle.Current()
   372  			if err == nil {
   373  				fmt.Println(manout.MessageCln("add ", manout.ForeBlue, dir))
   374  				configure.GetGlobalConfig().AddPath(dir)
   375  				configure.GetGlobalConfig().SaveConfiguration()
   376  				FindWorkspaceInfoByTemplate(nil) // this is parsing all templates in all workspaces and updates the project Infos
   377  			}
   378  		},
   379  	}
   380  
   381  	removePath = &cobra.Command{
   382  		Use:   "rm",
   383  		Short: "remove current path (pwd) from the current workspace",
   384  		Run: func(cmd *cobra.Command, args []string) {
   385  			checkDefaultFlags(cmd, args)
   386  			dir, err := dirhandle.Current()
   387  			if err == nil {
   388  				fmt.Println(manout.MessageCln("try to remove ", manout.ForeBlue, dir, manout.CleanTag, " from workspace"))
   389  				removed := configure.GetGlobalConfig().RemovePath(dir)
   390  				if !removed {
   391  					fmt.Println(manout.MessageCln(manout.ForeRed, "error", manout.CleanTag, " path is not part of the current workspace"))
   392  					systools.Exit(1)
   393  				} else {
   394  					fmt.Println(manout.MessageCln(manout.ForeGreen, "success"))
   395  					configure.GetGlobalConfig().SaveConfiguration()
   396  				}
   397  			}
   398  		},
   399  	}
   400  
   401  	createCmd = &cobra.Command{
   402  		Use:   "create",
   403  		Short: "create taskfile templates",
   404  		Run: func(cmd *cobra.Command, args []string) {
   405  			checkDefaultFlags(cmd, args)
   406  			WriteTemplate()
   407  		},
   408  	}
   409  
   410  	createImport = &cobra.Command{
   411  		Use:   "import",
   412  		Short: "Create importfile that can be used for templating",
   413  		Run: func(cmd *cobra.Command, args []string) {
   414  			checkDefaultFlags(cmd, args)
   415  			if len(args) == 0 {
   416  				fmt.Println("No paths submitted")
   417  				systools.Exit(1)
   418  			}
   419  			_, path, exists, terr := GetTemplate()
   420  			if terr != nil {
   421  				fmt.Println(manout.MessageCln(manout.ForeRed, "Error ", manout.CleanTag, terr.Error()))
   422  				systools.Exit(33)
   423  				return
   424  			}
   425  			if exists {
   426  				for _, addPath := range args {
   427  					err := CreateImport(path, addPath)
   428  					if err != nil {
   429  						fmt.Println("Error adding imports:", err)
   430  						systools.Exit(1)
   431  					}
   432  				}
   433  			} else {
   434  				fmt.Println("no taskfile exists. create these first by contxt create")
   435  				systools.Exit(1)
   436  			}
   437  
   438  		},
   439  	}
   440  
   441  	versionCmd = &cobra.Command{
   442  		Use:   "version",
   443  		Short: "prints current version",
   444  		Run: func(cmd *cobra.Command, args []string) {
   445  			checkDefaultFlags(cmd, args)
   446  			fmt.Println("version", configure.GetVersion(), "build", configure.GetBuild())
   447  		},
   448  	}
   449  
   450  	exportCmd = &cobra.Command{
   451  		Use:   "export",
   452  		Short: "exports the script section of an target like a bash script",
   453  		Long: `for extracting tasks commands in a format that can be executed as a shell script.
   454  this will be a plain export without handling dynamic generated placeholders (default placeholders will be parsed)  and contxt macros.
   455  also go-template imports will be handled.
   456  		`,
   457  		Run: func(cmd *cobra.Command, args []string) {
   458  			checkDefaultFlags(cmd, args)
   459  			for _, target := range args {
   460  				outStr, err := ExportTask(target)
   461  				if err == nil {
   462  					fmt.Println("# --- -------------- ---------- ----- ------ ")
   463  					fmt.Println("# --- contxt export of target " + target)
   464  					fmt.Println("# --- -------------- ---------- ----- ------ ")
   465  					fmt.Println()
   466  					fmt.Println(HandlePlaceHolder(outStr))
   467  				} else {
   468  					panic(err)
   469  				}
   470  
   471  			}
   472  		},
   473  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   474  			if len(args) != 0 {
   475  				return nil, cobra.ShellCompDirectiveNoFileComp
   476  			}
   477  			//targets, found := targetsAsMap()
   478  			targets, found := GetAllTargets()
   479  			if !found {
   480  				return nil, cobra.ShellCompDirectiveNoFileComp
   481  			}
   482  			return targets, cobra.ShellCompDirectiveNoFileComp
   483  		},
   484  	}
   485  
   486  	lintCmd = &cobra.Command{
   487  		Use:   "lint",
   488  		Short: "checking the task file",
   489  		Long: `to check if the task file contains the expected changes.
   490  use --full to see properties they are nor used.
   491  you will also see if a unexpected propertie found `,
   492  		Run: func(cmd *cobra.Command, args []string) {
   493  			checkDefaultFlags(cmd, args)
   494  			leftLen, _ := cmd.Flags().GetInt("left")
   495  			rightLen, _ := cmd.Flags().GetInt("right")
   496  			showall, _ := cmd.Flags().GetBool("full")
   497  			yamlParse, _ := cmd.Flags().GetBool("yaml")
   498  			yamlIndent, _ := cmd.Flags().GetInt("indent")
   499  			okay := false
   500  			if yamlParse {
   501  				ShowAsYaml(true, false, yamlIndent)
   502  				okay = LintOut(leftLen, 0, false, true)
   503  			} else {
   504  				okay = LintOut(leftLen, rightLen, showall, false)
   505  			}
   506  
   507  			if !okay {
   508  				systools.Exit(1)
   509  			}
   510  
   511  		},
   512  	}
   513  
   514  	installCmd = &cobra.Command{
   515  		Use:   "install",
   516  		Short: "install shell functions",
   517  		Long: `updates shell related files to get contxt running
   518  		as shortcut ctx. this will allow changing directories depending
   519  		on a context switch.
   520  		`,
   521  		Run: func(cmd *cobra.Command, args []string) {
   522  			checkDefaultFlags(cmd, args)
   523  		},
   524  	}
   525  
   526  	installBashRc = &cobra.Command{
   527  		Use:   "bashrc",
   528  		Short: "updates bashrc for using ctx alias",
   529  		Long: `writes needed functions into the users private .bashrc file.
   530  		This includes code completion and the ctx alias.
   531  		`,
   532  		Run: func(_ *cobra.Command, _ []string) {
   533  			BashUser()
   534  		},
   535  	}
   536  
   537  	installFish = &cobra.Command{
   538  		Use:   "fish",
   539  		Short: "create fish shell env for ctx",
   540  		Long: `create needed fish functions, auto completion for ctx
   541  		`,
   542  		Run: func(cmd *cobra.Command, _ []string) {
   543  			FishUpdate(cmd)
   544  		},
   545  	}
   546  
   547  	installZsh = &cobra.Command{
   548  		Use:   "zsh",
   549  		Short: "create zsh shell env for ctx",
   550  		Long: `create needed zsh functions and auto completion for zsh
   551  		`,
   552  		Run: func(cmd *cobra.Command, _ []string) {
   553  			ZshUpdate(cmd)
   554  		},
   555  	}
   556  
   557  	installPwrShell = &cobra.Command{
   558  		Use:   "powershell",
   559  		Short: "create powershell shell functions",
   560  		Long: `create needed powershell functions and auto completion for powershell.
   561  		for powershell the ctx shortcut is not aviable right now.
   562  		`,
   563  		Run: func(cmd *cobra.Command, _ []string) {
   564  			PwrShellUpdate(cmd)
   565  		},
   566  	}
   567  
   568  	runCmd = &cobra.Command{
   569  		Use:   "run",
   570  		Short: "run a target in contxt.yml task file",
   571  		Run: func(cmd *cobra.Command, args []string) {
   572  			checkDefaultFlags(cmd, args)
   573  			checkRunFlags(cmd, args)
   574  			GetLogger().WithField("args", args).Info("Run triggered")
   575  			GetLogger().WithField("all", runAtAll).Info("all workspaces?")
   576  
   577  			// set variables by argument
   578  			for preKey, preValue := range preVars {
   579  				GetLogger().WithFields(logrus.Fields{"key": preKey, "val": preValue}).Info("prevalue set by argument")
   580  				SetPH(preKey, preValue)
   581  			}
   582  
   583  			if len(args) == 0 {
   584  				printTargets()
   585  			}
   586  
   587  			for _, arg := range args {
   588  				GetLogger().WithField("target", arg).Info("try to run target")
   589  
   590  				path, err := dirhandle.Current()
   591  				if err == nil {
   592  					if runAtAll {
   593  						configure.GetGlobalConfig().PathWorkerNoCd(func(_ string, path string) {
   594  							GetLogger().WithField("path", path).Info("change dir")
   595  							os.Chdir(path)
   596  							runTargets(path, arg)
   597  						})
   598  					} else {
   599  						runTargets(path, arg)
   600  					}
   601  				}
   602  			}
   603  
   604  		},
   605  		ValidArgsFunction: func(_ *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
   606  			if len(args) != 0 {
   607  				return nil, cobra.ShellCompDirectiveNoFileComp
   608  			}
   609  			targets, found := GetAllTargets()
   610  			if !found {
   611  				return nil, cobra.ShellCompDirectiveNoFileComp
   612  			}
   613  			return targets, cobra.ShellCompDirectiveNoFileComp
   614  		},
   615  	}
   616  	sharedCmd = &cobra.Command{
   617  		Use:   "shared",
   618  		Short: "manage shared tasks",
   619  		Run: func(cmd *cobra.Command, args []string) {
   620  			checkDefaultFlags(cmd, args)
   621  		},
   622  	}
   623  
   624  	sharedListCmd = &cobra.Command{
   625  		Use:   "list",
   626  		Short: "list local shared tasks",
   627  		Run: func(cmd *cobra.Command, args []string) {
   628  			checkDefaultFlags(cmd, args)
   629  			sharedDirs, _ := ListUseCases(false)
   630  			for _, sharedPath := range sharedDirs {
   631  				fmt.Println(sharedPath)
   632  			}
   633  		},
   634  	}
   635  
   636  	sharedUpdateCmd = &cobra.Command{
   637  		Use:   "update",
   638  		Short: "updates shared uses if possible (git based)",
   639  		Run: func(cmd *cobra.Command, args []string) {
   640  			checkDefaultFlags(cmd, args)
   641  			useCases, err := ListUseCases(true)
   642  			if err == nil {
   643  				for _, path := range useCases {
   644  					fmt.Println(manout.MessageCln("check usage ", manout.ForeCyan, path))
   645  					UpdateUseCase(path)
   646  				}
   647  			}
   648  		},
   649  	}
   650  )
   651  
   652  func checkRunFlags(cmd *cobra.Command, _ []string) {
   653  	runAtAll, _ = cmd.Flags().GetBool("all-paths")
   654  	showInvTarget, _ = cmd.Flags().GetBool("all-targets")
   655  }
   656  
   657  func checkDirFlags(cmd *cobra.Command, _ []string) {
   658  
   659  	if pindex, err := cmd.Flags().GetString("index"); err == nil {
   660  		configure.GetGlobalConfig().ChangeActivePath(pindex)
   661  	}
   662  
   663  	clearTask, _ = cmd.Flags().GetBool("clear")
   664  	deleteWs, _ = cmd.Flags().GetString("delete")
   665  	setWs, _ = cmd.Flags().GetString("workspace")
   666  	uselastIndex, _ = cmd.Flags().GetBool("last")
   667  }
   668  
   669  func checkDefaultFlags(cmd *cobra.Command, _ []string) {
   670  	color, err := cmd.Flags().GetBool("coloroff")
   671  	if err == nil && color {
   672  		manout.ColorEnabled = false
   673  	}
   674  
   675  	loglevel, _ = cmd.Flags().GetString("loglevel")
   676  	setLoggerByArg()
   677  }
   678  
   679  func initCobra() {
   680  	// create dir command
   681  	dirCmd.AddCommand(showPaths)
   682  	dirCmd.AddCommand(addPaths)
   683  	dirCmd.AddCommand(listPaths)
   684  	dirCmd.AddCommand(removePath)
   685  	dirCmd.AddCommand(findPath)
   686  
   687  	dirCmd.Flags().IntVarP(&pathIndex, "index", "i", -1, "get path by the index in order the paths are stored")
   688  	dirCmd.Flags().BoolP("clear", "C", false, "remove all path assigments")
   689  	dirCmd.Flags().BoolP("last", "l", false, "get last used path index number")
   690  	dirCmd.Flags().StringP("delete", "d", "", "remove workspace")
   691  	dirCmd.Flags().StringP("workspace", "w", "", "set workspace. if not exists a new workspace will be created")
   692  
   693  	runCmd.Flags().BoolP("all-paths", "a", false, "run targets in all paths in the current workspace")
   694  	runCmd.Flags().Bool("all-targets", false, "show all targets. including invisible")
   695  	runCmd.Flags().StringToStringVarP(&preVars, "var", "v", nil, "set variables by keyname and value.")
   696  
   697  	createCmd.AddCommand(createImport)
   698  
   699  	//rootCmd.PersistentFlags().BoolVarP(&Experimental, "experimental", "E", true, "enable experimental features")
   700  	rootCmd.PersistentFlags().BoolVarP(&showColors, "coloroff", "c", false, "disable usage of colors in output")
   701  	rootCmd.PersistentFlags().BoolVarP(&showHints, "nohints", "n", false, "disable printing hints")
   702  	rootCmd.PersistentFlags().StringVar(&loglevel, "loglevel", "FATAL", "set loglevel")
   703  	rootCmd.AddCommand(dirCmd)
   704  	rootCmd.AddCommand(runCmd)
   705  	rootCmd.AddCommand(createCmd)
   706  	rootCmd.AddCommand(versionCmd)
   707  	rootCmd.AddCommand(exportCmd)
   708  
   709  	lintCmd.Flags().IntVar(&leftLen, "left", 45, "set the width for the source code")
   710  	lintCmd.Flags().IntVar(&rightLen, "right", 55, "set the witdh for the current state view")
   711  	lintCmd.Flags().IntVar(&yamlIndent, "indent", 2, "set indent for yaml output by using lint --yaml")
   712  	lintCmd.Flags().Bool("full", false, "print also unset properties")
   713  	lintCmd.Flags().Bool("yaml", false, "display parsed taskfile as yaml file")
   714  	lintCmd.Flags().Bool("parse", false, "parse second level keywords (#@...)")
   715  
   716  	rootCmd.AddCommand(lintCmd)
   717  
   718  	rootCmd.AddCommand(completionCmd)
   719  	rootCmd.AddCommand(gotoCmd)
   720  
   721  	installPwrShell.Flags().Bool("create-profile", false, "create a profile for powershell if not exists already")
   722  	installCmd.AddCommand(installBashRc)
   723  	installCmd.AddCommand(installFish)
   724  	installCmd.AddCommand(installZsh)
   725  	installCmd.AddCommand(installPwrShell)
   726  	rootCmd.AddCommand(installCmd)
   727  
   728  	workspaceCmd.AddCommand(wsNewCmd, wsRmCmd, wsListCmd, wsScanCmd, wsUseCmd)
   729  	rootCmd.AddCommand(workspaceCmd)
   730  
   731  	sharedCmd.AddCommand(sharedListCmd)
   732  	sharedCmd.AddCommand(sharedUpdateCmd)
   733  	rootCmd.AddCommand(sharedCmd)
   734  
   735  }
   736  
   737  func setLoggerByArg() {
   738  	if loglevel != "" {
   739  		lvl, err := logrus.ParseLevel(loglevel)
   740  		if err != nil {
   741  			log.Fatal(err)
   742  		}
   743  		log.SetLevel(lvl)
   744  	}
   745  }
   746  
   747  func initLogger() {
   748  	//log.Out = os.Stdout
   749  	//log.SetLevel(logrus.DebugLevel)
   750  
   751  }
   752  
   753  func executeCobra() error {
   754  	return rootCmd.Execute()
   755  }
   756  
   757  // GetLogger is the main Logger instance
   758  func GetLogger() *logrus.Logger {
   759  	return log
   760  }
   761  
   762  func shortcuts() bool {
   763  	if len(os.Args) == 2 {
   764  
   765  		switch os.Args[1] {
   766  		case "dir", "run", "create", "version":
   767  			return false
   768  		default:
   769  			foundATask := doMagicParamOne(os.Args[1])
   770  			return foundATask
   771  
   772  		}
   773  	}
   774  	return false
   775  }
   776  
   777  func InitDefaultVars() {
   778  	SetPH("CTX_OS", configure.GetOs())
   779  	SetPH("CTX_VERSION", configure.GetVersion())
   780  	SetPH("CTX_BUILDNO", configure.GetBuild())
   781  	// on windows we have to deal with old powershell and cmd versions, the do not
   782  	// support ANSII.
   783  	if configure.GetOs() == "windows" {
   784  		manout.ColorEnabled = false         //  by default we turn off the colors.
   785  		if os.Getenv("CTX_COLOR") == "ON" { // then lets see if this should forced for beeing enabled by env-var
   786  			manout.ColorEnabled = true
   787  		} else {
   788  			// if not forced already we try to figure out, by oure own, if the powershell is able to support ANSII
   789  			// this is since version 7 the case
   790  			version := PwrShellExec(PWRSHELL_CMD_VERSION)
   791  			SetPH("CTX_PS_VERSION", version) // also setup varibale to have the PS version in place
   792  			if version >= "7" {
   793  				manout.ColorEnabled = true // enable colors if we have powershell equals or greater then 7
   794  			}
   795  		}
   796  	}
   797  	// we checking the console support
   798  	// and turn the color off again if we do not have an terminal
   799  	inTerminal := "YES"
   800  	if !term.IsTerminal(int(os.Stdout.Fd())) {
   801  		manout.ColorEnabled = false
   802  		inTerminal = "NO"
   803  	}
   804  	SetPH("CTX_IN_TERMINAL", inTerminal) // do we have terminal running?
   805  
   806  	if currentDir, err := os.Getwd(); err == nil { // location as var
   807  		SetPH("CTX_PWD", currentDir)
   808  	} else {
   809  		CtxOut("Critical error while reading directory", err)
   810  		systools.Exit(systools.ErrorBySystem)
   811  	}
   812  }
   813  
   814  func setWorkspaceVariables() {
   815  	SetPH("CTX_WS", configure.GetGlobalConfig().UsedV2Config.CurrentSet)
   816  	configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
   817  		for _, ws2 := range cfg.Paths {
   818  			setConfigVaribales(ws2, "WS")
   819  		}
   820  
   821  	})
   822  }
   823  
   824  // InitWsVariables is setting up variables depending the current found configuration (.contxt.yml)
   825  func InitWsVariables() {
   826  	setWorkspaceVariables()
   827  }
   828  
   829  func setConfigVaribales(wsInfo configure.WorkspaceInfoV2, varPrefix string) {
   830  	if wsInfo.Project != "" && wsInfo.Role != "" {
   831  		prefix := wsInfo.Project + "_" + wsInfo.Role
   832  		SetPH(varPrefix+"0_"+prefix, wsInfo.Path) // at least XXX0 without any version. this could be overwritten by other checkouts
   833  		if wsInfo.Version != "" {
   834  			// if version is set, we use them for avoid conflicts with different checkouts
   835  			if versionSan, err := systools.CheckForCleanString(wsInfo.Version); err == nil {
   836  				prefix += "_" + versionSan
   837  				// add it to ws1 as prefix for versionized keys
   838  				SetPH(varPrefix+"1_"+prefix, wsInfo.Path)
   839  			}
   840  		}
   841  	}
   842  }
   843  
   844  // FindWorkspaceInfoByTemplate is searching for a template file and if found, it will set the project and role
   845  // from the template file to the workspace info
   846  // this is only done if the workspace info is not set yet
   847  // this is automatically done on each workspace, if the workspace is not set yet
   848  // but only on the command switch and 'dir add'
   849  func FindWorkspaceInfoByTemplate(updateFn func(workspace string, cnt int, update bool, info configure.WorkspaceInfoV2)) (allCount int, updatedCount int) {
   850  	wsCount := 0
   851  	wsUpdated := 0
   852  	if currentPath, err := os.Getwd(); err != nil {
   853  		CtxOut("Error while reading current directory", err)
   854  		systools.Exit(systools.ErrorBySystem)
   855  	} else {
   856  		haveUpdate := false
   857  		configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
   858  			wsCount++
   859  			for pathIndex, ws2 := range cfg.Paths {
   860  				if err := os.Chdir(ws2.Path); err == nil && ws2.Project == "" && ws2.Role == "" {
   861  					template, _, found, err := GetTemplate()
   862  					if found && err == nil {
   863  						if template.Workspace.Project != "" && template.Workspace.Role != "" {
   864  							ws2.Project = template.Workspace.Project
   865  							ws2.Role = template.Workspace.Role
   866  							cfg.Paths[pathIndex] = ws2
   867  							CtxOut("Found template for workspace ", index, " and set project and role to ", ws2.Project, ":", ws2.Role)
   868  							configure.GetGlobalConfig().UpdateCurrentConfig(cfg)
   869  							haveUpdate = true
   870  							wsUpdated++
   871  							if updateFn != nil {
   872  								updateFn(index, wsCount, true, ws2)
   873  							}
   874  						}
   875  					} else {
   876  						if updateFn != nil {
   877  							updateFn(index, wsCount, false, ws2)
   878  						}
   879  					}
   880  				}
   881  			}
   882  
   883  		})
   884  		if haveUpdate {
   885  			configure.GetGlobalConfig().SaveConfiguration()
   886  		}
   887  		os.Chdir(currentPath)
   888  	}
   889  	return wsCount, wsUpdated
   890  }
   891  
   892  // MainInit initilaize the Application.
   893  // this is required for any entrie-point
   894  // currently we have two of them.
   895  // by running in interactive in ishell, and by running with parameters.
   896  func MainInit() {
   897  	ResetVariables()                                         // needed because we could run in a shell
   898  	pathIndex = -1                                           // this is the path index used for the current path. -1 means unset
   899  	initLogger()                                             // init the logger. currently there is nothing happens except sometime for local debug
   900  	InitDefaultVars()                                        // init all the default variables first, they are independend from any configuration
   901  	CopyPlaceHolder2Origin()                                 // doing this 1/2 to have the current variables already in palce until we parse the config
   902  	var configErr = configure.GetGlobalConfig().InitConfig() // try to initialize current config
   903  	if configErr != nil {
   904  		log.Fatal(configErr)
   905  	}
   906  	InitWsVariables()        // like InitDefaultVars but these variables needs the configuration initalized
   907  	CopyPlaceHolder2Origin() // make placeholders usable as golang/template again
   908  }
   909  
   910  // MainExecute runs main. parsing flags
   911  func MainExecute() {
   912  	MainInit()
   913  	// first handle shortcuts
   914  	// before we get cobra controll
   915  	if !shortcuts() {
   916  		initCobra()
   917  		err := executeCobra()
   918  		if err != nil {
   919  			manout.Error("error", err)
   920  			systools.Exit(systools.ErrorInitApp)
   921  		}
   922  
   923  	}
   924  
   925  }
   926  
   927  func CallBackOldWs(oldws string) bool {
   928  	GetLogger().Info("OLD workspace: ", oldws)
   929  	// get all paths first
   930  	configure.GetGlobalConfig().PathWorkerNoCd(func(_ string, path string) {
   931  
   932  		os.Chdir(path)
   933  		template, templateFile, exists, _ := GetTemplate()
   934  
   935  		GetLogger().WithFields(logrus.Fields{
   936  			"templateFile": templateFile,
   937  			"exists":       exists,
   938  			"path":         path,
   939  		}).Debug("path parsing")
   940  
   941  		if exists && template.Config.Autorun.Onleave != "" {
   942  			onleaveTarget := template.Config.Autorun.Onleave
   943  			GetLogger().WithFields(logrus.Fields{
   944  				"templateFile": templateFile,
   945  				"target":       onleaveTarget,
   946  			}).Info("execute leave-action")
   947  			RunTargets(onleaveTarget, true)
   948  
   949  		}
   950  
   951  	})
   952  	return true
   953  }
   954  
   955  func GetColorEnabled() bool {
   956  	return showColors
   957  }
   958  
   959  func SetColorEnabled(enabled bool) {
   960  	showColors = enabled
   961  }
   962  
   963  func CallBackNewWs(newWs string) {
   964  	ResetVariables() // reset old variables while change the workspace. (req for shell mode)
   965  	MainInit()       // initialize the workspace
   966  	GetLogger().Info("NEW workspace: ", newWs)
   967  	configure.GetGlobalConfig().PathWorker(func(_ string, path string) { // iterate any path
   968  		template, templateFile, exists, _ := GetTemplate()
   969  
   970  		GetLogger().WithFields(logrus.Fields{
   971  			"templateFile": templateFile,
   972  			"exists":       exists,
   973  			"path":         path,
   974  		}).Debug("path parsing")
   975  
   976  		// try to run onEnter func at any possible target in the workspace
   977  		if exists && template.Config.Autorun.Onenter != "" {
   978  			onEnterTarget := template.Config.Autorun.Onenter
   979  			GetLogger().WithFields(logrus.Fields{
   980  				"templateFile": templateFile,
   981  				"target":       onEnterTarget,
   982  			}).Info("execute enter-action")
   983  			RunTargets(onEnterTarget, true)
   984  		}
   985  
   986  	}, func(origin string) {
   987  		GetLogger().WithFields(logrus.Fields{
   988  			"current-dir": origin,
   989  		}).Debug("done calling autoruns on sub-dirs")
   990  	})
   991  }
   992  
   993  func doMagicParamOne(param string) bool {
   994  	result := false
   995  	if param == "show-the-rainbow" {
   996  		systools.TestColorCombinations()
   997  		return true
   998  	}
   999  	// param is a workspace ?
  1000  	configure.GetGlobalConfig().ExecOnWorkSpaces(func(index string, cfg configure.ConfigurationV2) {
  1001  		if param == index {
  1002  			configure.GetGlobalConfig().ChangeWorkspace(index, CallBackOldWs, CallBackNewWs)
  1003  			result = true
  1004  		}
  1005  	})
  1006  
  1007  	return result
  1008  }
  1009  
  1010  func runTargets(_ string, targets string) {
  1011  	RunTargets(targets, true)
  1012  }
  1013  
  1014  func printOutHeader() {
  1015  	fmt.Println(manout.MessageCln(manout.BoldTag, manout.ForeWhite, "cont(e)xt ", manout.CleanTag, configure.GetVersion()))
  1016  	fmt.Println(manout.MessageCln(manout.Dim, " build-no [", manout.ResetDim, configure.GetBuild(), manout.Dim, "]"))
  1017  	if configure.GetOs() == "windows" {
  1018  		fmt.Println(manout.MessageCln(manout.BoldTag, manout.ForeWhite, " powershell version ", manout.CleanTag, GetPH("CTX_PS_VERSION")))
  1019  	}
  1020  }
  1021  
  1022  func printInfo() {
  1023  	printOutHeader()
  1024  	printPaths()
  1025  }
  1026  
  1027  func ResetVariables() {
  1028  	ClearAll()     // clears all placeholders
  1029  	ClearAllData() // clears all stored maps
  1030  }