github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/cmd/jiri/grep.go (about) 1 // Copyright 2016 The Fuchsia Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package main 6 7 import ( 8 "fmt" 9 "os" 10 "path/filepath" 11 12 "github.com/btwiuse/jiri" 13 "github.com/btwiuse/jiri/cmdline" 14 "github.com/btwiuse/jiri/gitutil" 15 "github.com/btwiuse/jiri/project" 16 ) 17 18 var cmdGrep = &cmdline.Command{ 19 Runner: jiri.RunnerFunc(runGrep), 20 Name: "grep", 21 Short: "Search across projects.", 22 Long: ` 23 Run git grep across all projects. 24 `, 25 ArgsName: "<query> [--] [<pathspec>...]", 26 } 27 28 var grepFlags struct { 29 cwdRel bool 30 n bool 31 h bool 32 i bool 33 e string 34 l bool 35 L bool 36 w bool 37 } 38 39 func init() { 40 flags := &cmdGrep.Flags 41 flags.BoolVar(&grepFlags.n, "n", false, "Prefix the line number to matching lines") 42 flags.StringVar(&grepFlags.e, "e", "", "The next parameter is the pattern. This option has to be used for patterns starting with -") 43 flags.BoolVar(&grepFlags.h, "H", true, "Does nothing. Just makes this git grep compatible") 44 flags.BoolVar(&grepFlags.i, "i", false, "Ignore case differences between the patterns and the files") 45 flags.BoolVar(&grepFlags.l, "l", false, "Instead of showing every matched line, show only the names of files that contain matches") 46 flags.BoolVar(&grepFlags.w, "w", false, "Match the pattern only at word boundary") 47 flags.BoolVar(&grepFlags.l, "name-only", false, "same as -l") 48 flags.BoolVar(&grepFlags.l, "files-with-matches", false, "same as -l") 49 flags.BoolVar(&grepFlags.L, "L", false, "Instead of showing every matched line, show only the names of files that do not contain matches") 50 flags.BoolVar(&grepFlags.L, "files-without-match", false, "same as -L") 51 flags.BoolVar(&grepFlags.cwdRel, "cwd-rel", false, "Output paths relative to the current working directory (if available)") 52 } 53 54 func buildFlags() []string { 55 var args []string 56 if grepFlags.n { 57 args = append(args, "-n") 58 } 59 if grepFlags.e != "" { 60 args = append(args, "-e", grepFlags.e) 61 } 62 if grepFlags.i { 63 args = append(args, "-i") 64 } 65 if grepFlags.l { 66 args = append(args, "-l") 67 } 68 if grepFlags.L { 69 args = append(args, "-L") 70 } 71 if grepFlags.w { 72 args = append(args, "-w") 73 } 74 return args 75 } 76 77 func doGrep(jirix *jiri.X, args []string) ([]string, error) { 78 var pathSpecs []string 79 lenArgs := len(args) 80 if lenArgs > 0 { 81 for i, a := range os.Args { 82 if a == "--" { 83 pathSpecs = os.Args[i+1:] 84 break 85 } 86 } 87 // we will not find -- if user uses something like jiri grep -- a b, 88 // as flag.Parse() removes '--' in that case, so set args length 89 lenArgs = len(args) - len(pathSpecs) 90 for i, a := range args { 91 92 if a == "--" { 93 args = args[0:i] 94 // reset length 95 lenArgs = len(args) 96 break 97 } 98 } 99 } 100 101 if grepFlags.e != "" && lenArgs > 0 { 102 return nil, jirix.UsageErrorf("No additional argument allowed with flag -e") 103 } else if grepFlags.e == "" && lenArgs != 1 { 104 return nil, jirix.UsageErrorf("grep requires one argument") 105 } 106 107 projects, err := project.LocalProjects(jirix, project.FastScan) 108 if err != nil { 109 return nil, err 110 } 111 112 // TODO(ianloic): run in parallel rather than serially. 113 // TODO(ianloic): only run grep on projects under the cwd. 114 var results []string 115 flags := buildFlags() 116 if jirix.Color.Enabled() { 117 flags = append(flags, "--color=always") 118 } 119 query := "" 120 if lenArgs == 1 { 121 query = args[0] 122 } 123 124 cwd := jirix.Root 125 if grepFlags.cwdRel { 126 if wd, err := os.Getwd(); err == nil { 127 cwd = wd 128 } 129 } 130 131 for _, project := range projects { 132 relpath, err := filepath.Rel(cwd, project.Path) 133 if err != nil { 134 return nil, err 135 } 136 git := gitutil.New(jirix, gitutil.RootDirOpt(project.Path)) 137 lines, err := git.Grep(query, pathSpecs, flags...) 138 if err != nil { 139 continue 140 } 141 for _, line := range lines { 142 // TODO(ianloic): higlight the project path part like `repo grep`. 143 results = append(results, relpath+"/"+line) 144 } 145 } 146 147 // TODO(ianloic): fail if all of the sub-greps fail 148 return results, nil 149 } 150 151 func runGrep(jirix *jiri.X, args []string) error { 152 lines, err := doGrep(jirix, args) 153 if err != nil { 154 return err 155 } 156 157 for _, line := range lines { 158 fmt.Println(line) 159 } 160 return nil 161 }