Repository: getgauge/gauge (go)

Issue:
Get list of steps via `gauge list`
### Discussed in https://github.com/getgauge/gauge/discussions/2423

<div type='discussions-op-text'>

<sup>Originally posted by **EGTomKies** October 27, 2023</sup>
I know it loads in VSC Gauge will pre-register steps.  

Is there any way to utilize that to get a list of steps outputted to a file?  I'm trying to make a list for my user's benefit.  

Going line by line over the file and pulling out the steps in lines starting with `@step` is easy enough, but I'm having issues getting at the ones inside an array.  

Before I reinvent the wheel, I figured I'd ask if there's a CLI tool I may be overlooking like the one that can out put a full list of specs.  </div>

Pull request description:
add support to print list of implemented steps
usage: gauge list --steps

fixes #2432 

Signed-off-by: sriv <srikanth.ddit@gmail.com>


Patch:
diff --git a/cmd/list.go b/cmd/list.go
index fffe19da..75882c1f 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -10,7 +10,11 @@ import (
 	"fmt"
 	supersort "sort"
 
+	gm "github.com/getgauge/gauge-proto/go/gauge_messages"
 	"github.com/getgauge/gauge/config"
+	"github.com/getgauge/gauge/manifest"
+	"github.com/getgauge/gauge/runner"
+	"github.com/getgauge/gauge/util"
 
 	"github.com/getgauge/gauge/filter"
 	"github.com/getgauge/gauge/gauge"
@@ -22,8 +26,8 @@ import (
 var (
 	listCmd = &cobra.Command{
 		Use:     "list [flags] [args]",
-		Short:   "List specifications, scenarios or tags for a gauge project",
-		Long:    `List specifications, scenarios or tags for a gauge project`,
+		Short:   "List specifications, scenarios, steps or tags for a gauge project",
+		Long:    `List specifications, scenarios, steps or tags for a gauge project`,
 		Example: `  gauge list --tags specs`,
 		Run: func(cmd *cobra.Command, args []string) {
 			if err := config.SetProjectRoot(args); err != nil {
@@ -46,7 +50,11 @@ var (
 				logger.Info(true, "[Tags]")
 				listTags(specs, print)
 			}
-			if !specsFlag && !scenariosFlag && !tagsFlag {
+			if stepsFlag {
+				logger.Info(true, "[Steps]")
+				listSteps(specs, print)
+			}
+			if !specsFlag && !scenariosFlag && !tagsFlag && !stepsFlag {
 				exit(fmt.Errorf("Missing flag, nothing to list"), cmd.UsageString())
 			}
 		},
@@ -55,6 +63,7 @@ var (
 	tagsFlag      bool
 	specsFlag     bool
 	scenariosFlag bool
+	stepsFlag     bool
 )
 
 func init() {
@@ -62,6 +71,7 @@ func init() {
 	listCmd.Flags().BoolVarP(&tagsFlag, "tags", "", false, "List the tags in projects")
 	listCmd.Flags().BoolVarP(&specsFlag, "specs", "", false, "List the specifications in projects")
 	listCmd.Flags().BoolVarP(&scenariosFlag, "scenarios", "", false, "List the scenarios in projects")
+	listCmd.Flags().BoolVarP(&stepsFlag, "steps", "", false, "List all the steps in projects (including concept steps). Does not include unused steps.")
 }
 
 type handleResult func([]string)
@@ -96,6 +106,10 @@ func listSpecifications(s []*gauge.Specification, f handleResult) {
 	f(sortedDistinctElements(allSpecs))
 }
 
+func listSteps(s []*gauge.Specification, f handleResult) {
+	f(sortedDistinctElements(getImplementedStepsWithAliases()))
+}
+
 func sortedDistinctElements(s []string) []string {
 	unique := uniqueNonEmptyElementsOf(s)
 	supersort.Strings(unique)
@@ -120,5 +134,31 @@ func uniqueNonEmptyElementsOf(input []string) []string {
 	}
 
 	return us
+}
+
+func getImplementedStepsWithAliases() []string {
+	r, err := connectToRunner()
+	defer func() { _ = r.Kill() }()
+	if err != nil {
+		panic(err)
+	}
+	getAllStepsRequest := &gm.Message{MessageType: gm.Message_StepNamesRequest, StepNamesRequest: &gm.StepNamesRequest{}}
+	response, err := r.ExecuteMessageWithTimeout(getAllStepsRequest)
+	if err != nil {
+		exit(fmt.Errorf("error while connecting to runner : %s", err.Error()), "unable to get steps from runner")
+	}
+	return response.GetStepNamesResponse().GetSteps()
+}
+
+var connectToRunner = func() (runner.Runner, error) {
+	outFile, err := util.OpenFile(logger.ActiveLogFile)
+	if err != nil {
+		return nil, err
+	}
+	manifest, err := manifest.ProjectManifest()
+	if err != nil {
+		return nil, err
+	}
 
+	return runner.StartGrpcRunner(manifest, outFile, outFile, config.IdeRequestTimeout(), false)
 }
diff --git a/cmd/mockRunner.go b/cmd/mockRunner.go
new file mode 100644
index 00000000..24dbb3d6
--- /dev/null
+++ b/cmd/mockRunner.go
@@ -0,0 +1,50 @@
+/*----------------------------------------------------------------
+ *  Copyright (c) ThoughtWorks, Inc.
+ *  Licensed under the Apache License, Version 2.0
+ *  See LICENSE in the project root for license information.
+ *----------------------------------------------------------------*/
+
+package cmd
+
+import (
+	"net"
+
+	"github.com/getgauge/gauge-proto/go/gauge_messages"
+	"github.com/getgauge/gauge/runner"
+)
+
+type mockRunner struct {
+	response *gauge_messages.Message
+}
+
+func (r *mockRunner) ExecuteAndGetStatus(m *gauge_messages.Message) *gauge_messages.ProtoExecutionResult {
+	return nil
+}
+
+func (r *mockRunner) ExecuteMessageWithTimeout(m *gauge_messages.Message) (*gauge_messages.Message, error) {
+	return r.response, nil
+}
+
+func (r *mockRunner) Alive() bool {
+	return true
+}
+
+func (r *mockRunner) Kill() error {
+	return nil
+}
+
+func (r *mockRunner) Connection() net.Conn {
+	return nil
+}
+
+func (r *mockRunner) IsMultithreaded() bool {
+	return false
+}
+
+func (r *mockRunner) Info() *runner.RunnerInfo {
+	return &runner.RunnerInfo{Killed: false}
+}
+
+func (r *mockRunner) Pid() int {
+	return 0
+}
diff --git a/filter/specItemFilter.go b/filter/specItemFilter.go
index 133bdc7b..7ce41d15 100644
--- a/filter/specItemFilter.go
+++ b/filter/specItemFilter.go
@@ -8,7 +8,6 @@ package filter
 
 import (
 	"errors"
-	"github.com/getgauge/gauge/env"
 	"go/constant"
 	"go/token"
 	"go/types"
@@ -16,6 +15,8 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/getgauge/gauge/env"
+
 	"fmt"
 
 	"github.com/getgauge/gauge/gauge"
diff --git a/version/version.go b/version/version.go
index 125aac1b..370ef02e 100644
--- a/version/version.go
+++ b/version/version.go
@@ -14,7 +14,7 @@ import (
 )
 
 // CurrentGaugeVersion represents the current version of Gauge
-var CurrentGaugeVersion = &Version{1, 6, 6}
+var CurrentGaugeVersion = &Version{1, 6, 7}
 
 // BuildMetadata represents build information of current release (e.g, nightly build information)
 var BuildMetadata = ""


Test patch:
diff --git a/cmd/list_test.go b/cmd/list_test.go
index a87ea434..c9c73e11 100644
--- a/cmd/list_test.go
+++ b/cmd/list_test.go
@@ -10,7 +10,9 @@ import (
 	"reflect"
 	"testing"
 
+	"github.com/getgauge/gauge-proto/go/gauge_messages"
 	"github.com/getgauge/gauge/gauge"
+	"github.com/getgauge/gauge/runner"
 )
 
 func TestOnlyUniqueTagsAreReturned(t *testing.T) {
@@ -35,6 +37,32 @@ func TestOnlyUniqueSceanriosAreReturned(t *testing.T) {
 	})
 }
 
+func TestOnlyUniqueStepsAreReturned(t *testing.T) {
+	connectToRunner = func() (runner.Runner, error) {
+		return &mockRunner{
+			response: &gauge_messages.Message{
+				StepNamesResponse: &gauge_messages.StepNamesResponse{
+					Steps: []string{
+						"Vowels are <vowelString>.",
+						"Vowels in English language are <vowelString>.",
+						"The word <word> has <expectedCount> vowels.",
+						"Almost all words have vowels <wordsTable>",
+					},
+				},
+			},
+		}, nil
+	}
+	expected := []string{
+		"Almost all words have vowels <wordsTable>",
+		"The word <word> has <expectedCount> vowels.",
+		"Vowels are <vowelString>.",
+		"Vowels in English language are <vowelString>.",
+	}
+	listSteps([]*gauge.Specification{buildTestSpecification()}, func(res []string) {
+		verifyUniqueness(res, expected, t)
+	})
+}
+
 func buildTestSpecification() *gauge.Specification {
 	return &gauge.Specification{
 		Heading: &gauge.Heading{