| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package main |
| |
| import ( |
| "fmt" |
| "os" |
| "path/filepath" |
| |
| "fuchsia.googlesource.com/jiri" |
| "fuchsia.googlesource.com/jiri/cmdline" |
| "fuchsia.googlesource.com/jiri/gitutil" |
| "fuchsia.googlesource.com/jiri/project" |
| ) |
| |
| var cmdGrep = &cmdline.Command{ |
| Runner: jiri.RunnerFunc(runGrep), |
| Name: "grep", |
| Short: "Search across projects.", |
| Long: ` |
| Run git grep across all projects. |
| `, |
| ArgsName: "<query> [--] [<pathspec>...]", |
| } |
| |
| var grepFlags struct { |
| cwdRel bool |
| n bool |
| h bool |
| i bool |
| e string |
| l bool |
| L bool |
| w bool |
| } |
| |
| func init() { |
| flags := &cmdGrep.Flags |
| flags.BoolVar(&grepFlags.n, "n", false, "Prefix the line number to matching lines") |
| flags.StringVar(&grepFlags.e, "e", "", "The next parameter is the pattern. This option has to be used for patterns starting with -") |
| flags.BoolVar(&grepFlags.h, "H", true, "Does nothing. Just makes this git grep compatible") |
| flags.BoolVar(&grepFlags.i, "i", false, "Ignore case differences between the patterns and the files") |
| flags.BoolVar(&grepFlags.l, "l", false, "Instead of showing every matched line, show only the names of files that contain matches") |
| flags.BoolVar(&grepFlags.w, "w", false, "Match the pattern only at word boundary") |
| flags.BoolVar(&grepFlags.l, "name-only", false, "same as -l") |
| flags.BoolVar(&grepFlags.l, "files-with-matches", false, "same as -l") |
| flags.BoolVar(&grepFlags.L, "L", false, "Instead of showing every matched line, show only the names of files that do not contain matches") |
| flags.BoolVar(&grepFlags.L, "files-without-match", false, "same as -L") |
| flags.BoolVar(&grepFlags.cwdRel, "cwd-rel", false, "Output paths relative to the current working directory (if available)") |
| } |
| |
| func buildFlags() []string { |
| var args []string |
| if grepFlags.n { |
| args = append(args, "-n") |
| } |
| if grepFlags.e != "" { |
| args = append(args, "-e", grepFlags.e) |
| } |
| if grepFlags.i { |
| args = append(args, "-i") |
| } |
| if grepFlags.l { |
| args = append(args, "-l") |
| } |
| if grepFlags.L { |
| args = append(args, "-L") |
| } |
| if grepFlags.w { |
| args = append(args, "-w") |
| } |
| return args |
| } |
| |
| func doGrep(jirix *jiri.X, args []string) ([]string, error) { |
| var pathSpecs []string |
| lenArgs := len(args) |
| if lenArgs > 0 { |
| for i, a := range os.Args { |
| if a == "--" { |
| pathSpecs = os.Args[i+1:] |
| break |
| } |
| } |
| // we will not find -- if user uses something like jiri grep -- a b, |
| // as flag.Parse() removes '--' in that case, so set args length |
| lenArgs = len(args) - len(pathSpecs) |
| for i, a := range args { |
| |
| if a == "--" { |
| args = args[0:i] |
| // reset length |
| lenArgs = len(args) |
| break |
| } |
| } |
| } |
| |
| if grepFlags.e != "" && lenArgs > 0 { |
| return nil, jirix.UsageErrorf("No additional argument allowed with flag -e") |
| } else if grepFlags.e == "" && lenArgs != 1 { |
| return nil, jirix.UsageErrorf("grep requires one argument") |
| } |
| |
| projects, err := project.LocalProjects(jirix, project.FastScan) |
| if err != nil { |
| return nil, err |
| } |
| |
| // TODO(ianloic): run in parallel rather than serially. |
| // TODO(ianloic): only run grep on projects under the cwd. |
| var results []string |
| flags := buildFlags() |
| if jirix.Color.Enabled() { |
| flags = append(flags, "--color=always") |
| } |
| query := "" |
| if lenArgs == 1 { |
| query = args[0] |
| } |
| |
| cwd := jirix.Root |
| if grepFlags.cwdRel { |
| if wd, err := os.Getwd(); err == nil { |
| cwd = wd |
| } |
| } |
| |
| for _, project := range projects { |
| relpath, err := filepath.Rel(cwd, project.Path) |
| if err != nil { |
| return nil, err |
| } |
| git := gitutil.New(jirix, gitutil.RootDirOpt(project.Path)) |
| lines, err := git.Grep(query, pathSpecs, flags...) |
| if err != nil { |
| continue |
| } |
| for _, line := range lines { |
| // TODO(ianloic): higlight the project path part like `repo grep`. |
| results = append(results, relpath+"/"+line) |
| } |
| } |
| |
| // TODO(ianloic): fail if all of the sub-greps fail |
| return results, nil |
| } |
| |
| func runGrep(jirix *jiri.X, args []string) error { |
| lines, err := doGrep(jirix, args) |
| if err != nil { |
| return err |
| } |
| |
| for _, line := range lines { |
| fmt.Println(line) |
| } |
| return nil |
| } |