Browse Source

Add Description() to leaves and improve the CLI help

Signed-off-by: Vadim Markovtsev <vadim@sourced.tech>
Vadim Markovtsev 6 years ago
parent
commit
cb0ca5fe71

+ 1 - 1
.travis.yml

@@ -46,7 +46,7 @@ before_install:
   - go get -v github.com/golang/lint/golint
   - rm -rf $GOPATH/src/gopkg.in/src-d/go-git.v4
   - git clone --depth 1 --single-branch --branch $GOGIT_TAG https://github.com/src-d/go-git $GOPATH/src/gopkg.in/src-d/go-git.v4
-  - wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py --user && rm get-pip.py
+  - (wget https://bootstrap.pypa.io/get-pip.py || wget https://raw.githubusercontent.com/pypa/get-pip/master/get-pip.py) && python3 get-pip.py --user && rm get-pip.py
   - export PATH=~/usr/bin:$PATH
   - make --version
   - pip3 --version

+ 5 - 0
cmd/hercules/plugin.template

@@ -74,6 +74,11 @@ func ({{.varname}} *{{.name}}) Flag() string {
   return "{{.flag}}"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func ({{.varname}} *{{.name}}) Description() string {
+  return "TODO: explain what this analysis is doing."
+}
+
 // Configure applies the parameters specified in the command line. Map keys correspond to "Name".
 func ({{.varname}} *{{.name}}) Configure(facts map[string]interface{}) {
 }

+ 25 - 6
cmd/hercules/root.go

@@ -16,13 +16,14 @@ import (
 	_ "unsafe" // for go:linkname
 
 	"github.com/gogo/protobuf/proto"
+	"github.com/Masterminds/sprig"
 	"github.com/spf13/cobra"
 	"github.com/spf13/pflag"
 	"golang.org/x/crypto/ssh/terminal"
 	progress "gopkg.in/cheggaaa/pb.v1"
+	"gopkg.in/src-d/go-billy-siva.v4"
 	"gopkg.in/src-d/go-billy.v4/memfs"
 	"gopkg.in/src-d/go-billy.v4/osfs"
-	"gopkg.in/src-d/go-billy-siva.v4"
 	"gopkg.in/src-d/go-git.v4"
 	"gopkg.in/src-d/go-git.v4/plumbing/object"
 	"gopkg.in/src-d/go-git.v4/storage"
@@ -326,6 +327,7 @@ func formatUsage(c *cobra.Command) error {
 		"plumbing": plumbing,
 		"features": features,
 	}
+	cobra.AddTemplateFuncs(sprig.TxtFuncMap())
 
 	template := `Usage:{{if .c.Runnable}}
   {{.c.UseLine}}{{end}}{{if .c.HasAvailableSubCommands}}
@@ -341,17 +343,34 @@ Available Commands:{{range .c.Commands}}{{if (or .IsAvailableCommand (eq .Name "
   {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .c.HasAvailableLocalFlags}}
 
 Flags:
-{{.c.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}
+{{range $line := .c.LocalFlags.FlagUsages | trimTrailingWhitespaces | split "\n"}}
+{{- $desc := splitList "   " $line | last}}
+{{- $offset := sub ($desc | len) ($desc | trim | len)}}
+{{- $indent := splitList "   " $line | initial | join "   " | len | add 3 | add $offset | int}}
+{{- $wrap := sub 120 $indent | int}}
+{{- splitList "   " $line | initial | join "   "}}   {{cat "!" $desc | wrap $wrap | indent $indent | substr $indent -1 | substr 2 -1}}
+{{end}}{{end}}
 
 Analysis Targets:{{range .leaves}}
-      --{{rpad .Flag 40 }}Runs {{.Name}} analysis.{{range .ListConfigurationOptions}}
-          --{{if .Type.String}}{{rpad (print .Flag " " .Type.String) 40}}{{else}}{{rpad .Flag 40}}{{end}}{{.Description}}{{if .Default}} The default value is {{.FormatDefault}}.{{end}}{{end}}{{end}}
+      --{{rpad .Flag 40}}Runs {{.Name}} analysis.{{wrap 72 .Description | nindent 48}}{{range .ListConfigurationOptions}}
+          --{{if .Type.String}}{{rpad (print .Flag " " .Type.String) 40}}{{else}}{{rpad .Flag 40}}{{end}}
+          {{- $desc := dict "desc" .Description}}
+          {{- if .Default}}{{$_ := set $desc "desc" (print .Description " The default value is " .FormatDefault ".")}}
+          {{- end}}
+          {{- $desc := pluck "desc" $desc | first}}
+          {{- $desc | wrap 68 | indent 52 | substr 52 -1}}{{end}}
+{{end}}
 
 Plumbing Options:{{range .plumbing}}{{$name := .Name}}{{range .ListConfigurationOptions}}
-      --{{if .Type.String}}{{rpad (print .Flag " " .Type.String " [" $name "]") 40}}{{else}}{{rpad (print .Flag " [" $name "]") 40}}{{end}}{{.Description}}{{if .Default}} The default value is {{.FormatDefault}}.{{end}}{{end}}{{end}}
+      --{{if .Type.String}}{{rpad (print .Flag " " .Type.String " [" $name "]") 40}}{{else}}{{rpad (print .Flag " [" $name "]") 40}}
+        {{- end}}
+        {{- $desc := dict "desc" .Description}}
+        {{- if .Default}}{{$_ := set $desc "desc" (print .Description " The default value is " .FormatDefault ".")}}
+        {{- end}}
+        {{- $desc := pluck "desc" $desc | first}}{{$desc | wrap 72 | indent 48 | substr 48 -1}}{{end}}{{end}}
 
 --feature:{{range $key, $value := .features}}
-      {{rpad $key 40}}Enables {{range $index, $item := $value}}{{if $index}}, {{end}}{{$item.Name}}{{end}}.{{end}}{{if .c.HasAvailableInheritedFlags}}
+      {{rpad $key 42}}Enables {{range $index, $item := $value}}{{if $index}}, {{end}}{{$item.Name}}{{end}}.{{end}}{{if .c.HasAvailableInheritedFlags}}
 
 Global Flags:
 {{.c.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .c.HasHelpSubCommands}}

+ 5 - 0
contrib/_plugin_example/churn_analysis.go

@@ -98,6 +98,11 @@ func (churn *ChurnAnalysis) Flag() string {
 	return "churn"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (churn *ChurnAnalysis) Description() string {
+	return "Collects the daily numbers of inserted and removed lines."
+}
+
 // Configure applies the parameters specified in the command line. Map keys correspond to "Name".
 func (churn *ChurnAnalysis) Configure(facts map[string]interface{}) {
 	if val, exists := facts[ConfigChurnTrackPeople].(bool); exists {

+ 5 - 1
internal/core/pipeline.go

@@ -126,8 +126,12 @@ type FeaturedPipelineItem interface {
 // LeafPipelineItem corresponds to the top level pipeline items which produce the end results.
 type LeafPipelineItem interface {
 	PipelineItem
-	// Flag returns the cmdline name of the item.
+	// Flag returns the cmdline switch to run the analysis. Should be dash-lower-case
+	// without the leading dashes.
 	Flag() string
+	// Description returns the text which explains what the analysis is doing.
+	// Should start with a capital letter and end with a dot.
+	Description() string
 	// Finalize returns the result of the analysis.
 	Finalize() interface{}
 	// Serialize encodes the object returned by Finalize() to YAML or Protocol Buffers.

+ 8 - 0
internal/core/pipeline_test.go

@@ -59,6 +59,10 @@ func (item *testPipelineItem) Flag() string {
 	return "mytest"
 }
 
+func (item *testPipelineItem) Description() string {
+	return "description!"
+}
+
 func (item *testPipelineItem) Features() []string {
 	f := [...]string{"power"}
 	return f[:]
@@ -156,6 +160,10 @@ func (item *dependingTestPipelineItem) Flag() string {
 	return "depflag"
 }
 
+func (item *dependingTestPipelineItem) Description() string {
+	return "another description"
+}
+
 func (item *dependingTestPipelineItem) Consume(deps map[string]interface{}) (map[string]interface{}, error) {
 	_, exists := deps["test"]
 	item.DependencySatisfied = exists

+ 1 - 1
internal/core/registry.go

@@ -206,7 +206,7 @@ func (registry *PipelineItemRegistry) AddFlags(flagSet *pflag.FlagSet) (
 		iface = interface{}(true)
 		ptr2 := (**bool)(unsafe.Pointer(uintptr(unsafe.Pointer(&iface)) + unsafe.Sizeof(&iface)))
 		*ptr2 = flagSet.Bool("dry-run", false, "Do not run any analyses - only resolve the DAG. "+
-			"Useful for -dump-dag.")
+			"Useful for --dump-dag.")
 		flags[ConfigPipelineDryRun] = iface
 	}
 	features := []string{}

+ 5 - 0
internal/plumbing/uast/uast.go

@@ -510,6 +510,11 @@ func (saver *ChangesSaver) Flag() string {
 	return "dump-uast-changes"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (saver *ChangesSaver) Description() string {
+	return "Saves UASTs and file contents on disk for each commit."
+}
+
 // Configure sets the properties previously published by ListConfigurationOptions().
 func (saver *ChangesSaver) Configure(facts map[string]interface{}) {
 	if val, exists := facts[ConfigUASTChangesSaverOutputPath]; exists {

+ 6 - 0
leaves/burndown.go

@@ -221,6 +221,12 @@ func (analyser *BurndownAnalysis) Flag() string {
 	return "burndown"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (analyser *BurndownAnalysis) Description() string {
+	return "Line burndown stats indicate the numbers of lines which were last edited within " +
+		"specific time intervals through time. Search for \"git-of-theseus\" in the internet."
+}
+
 // Initialize resets the temporary caches and prepares this PipelineItem for a series of Consume()
 // calls. The repository which is going to be analysed is supplied as an argument.
 func (analyser *BurndownAnalysis) Initialize(repository *git.Repository) {

+ 7 - 0
leaves/comment_sentiment.go

@@ -112,6 +112,13 @@ func (sent *CommentSentimentAnalysis) Flag() string {
 	return "sentiment"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (sent *CommentSentimentAnalysis) Description() string {
+	return "Classifies each new or changed comment per commit as containing positive or " +
+		"negative emotions. The classifier outputs a real number between 0 and 1," +
+		"1 is the most positive and 0 is the most negative."
+}
+
 // Configure sets the properties previously published by ListConfigurationOptions().
 func (sent *CommentSentimentAnalysis) Configure(facts map[string]interface{}) {
 	if val, exists := facts[ConfigCommentSentimentGap]; exists {

+ 7 - 0
leaves/couples.go

@@ -94,6 +94,13 @@ func (couples *CouplesAnalysis) Flag() string {
 	return "couples"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (couples *CouplesAnalysis) Description() string {
+	return "The result is a square matrix, the value in each cell corresponds to the number " +
+		"of times the pair of files appeared in the same commit or pair of developers " +
+		"committed to the same file."
+}
+
 // Initialize resets the temporary caches and prepares this PipelineItem for a series of Consume()
 // calls. The repository which is going to be analysed is supplied as an argument.
 func (couples *CouplesAnalysis) Initialize(repository *git.Repository) {

+ 5 - 0
leaves/file_history.go

@@ -59,6 +59,11 @@ func (history *FileHistory) Flag() string {
 	return "file-history"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (history *FileHistory) Description() string {
+	return "Each file path is mapped to the list of commits which involve that file."
+}
+
 // Configure sets the properties previously published by ListConfigurationOptions().
 func (history *FileHistory) Configure(facts map[string]interface{}) {
 }

+ 8 - 0
leaves/shotness.go

@@ -121,6 +121,14 @@ func (shotness *ShotnessAnalysis) Flag() string {
 	return "shotness"
 }
 
+// Description returns the text which explains what the analysis is doing.
+func (shotness *ShotnessAnalysis) Description() string {
+	return "Structural hotness - the granular alternative to --couples. " +
+		"Given an XPath over UASTs - selecting functions by default - we build the square " +
+		"co-occurrence matrix. The value in each cell equals to the number of times the pair " +
+		"of selected UAST units appeared in the same commit."
+}
+
 // Configure sets the properties previously published by ListConfigurationOptions().
 func (shotness *ShotnessAnalysis) Configure(facts map[string]interface{}) {
 	if val, exists := facts[ConfigShotnessXpathStruct]; exists {