github.com/getgauge/gauge@v1.6.9/parser/resolver.go (about)

     1  /*----------------------------------------------------------------
     2   *  Copyright (c) ThoughtWorks, Inc.
     3   *  Licensed under the Apache License, Version 2.0
     4   *  See LICENSE in the project root for license information.
     5   *----------------------------------------------------------------*/
     6  
     7  package parser
     8  
     9  import (
    10  	"fmt"
    11  	"regexp"
    12  	"strings"
    13  
    14  	"github.com/getgauge/gauge-proto/go/gauge_messages"
    15  	"github.com/getgauge/gauge/gauge"
    16  	"github.com/getgauge/gauge/util"
    17  )
    18  
    19  type invalidSpecialParamError struct {
    20  	message string
    21  }
    22  
    23  type resolverFn func(string) (*gauge.StepArg, error)
    24  type specialTypeResolver struct {
    25  	predefinedResolvers map[string]resolverFn
    26  }
    27  
    28  func (invalidSpecialParamError invalidSpecialParamError) Error() string {
    29  	return invalidSpecialParamError.message
    30  }
    31  
    32  //Resolve takes a step, a lookup and updates the target after reconciling the dynamic paramters from the given lookup
    33  func Resolve(step *gauge.Step, parent *gauge.Step, lookup *gauge.ArgLookup, target *gauge_messages.ProtoStep) error {
    34  	stepParameters, err := getResolvedParams(step, parent, lookup)
    35  	if err != nil {
    36  		return err
    37  	}
    38  	paramIndex := 0
    39  	for fragmentIndex, fragment := range target.Fragments {
    40  		if fragment.GetFragmentType() == gauge_messages.Fragment_Parameter {
    41  			target.Fragments[fragmentIndex].Parameter = stepParameters[paramIndex]
    42  			paramIndex++
    43  		}
    44  	}
    45  	return nil
    46  }
    47  
    48  // getResolvedParams based on the arg type(static, dynamic, table, special_string, special_table) resolves the parameter of a step.
    49  func getResolvedParams(step *gauge.Step, parent *gauge.Step, lookup *gauge.ArgLookup) ([]*gauge_messages.Parameter, error) {
    50  	parameters := make([]*gauge_messages.Parameter, 0)
    51  	for _, arg := range step.Args {
    52  		parameter := new(gauge_messages.Parameter)
    53  		parameter.Name = arg.Name
    54  		if arg.ArgType == gauge.Static {
    55  			parameter.ParameterType = gauge_messages.Parameter_Static
    56  			parameter.Value = arg.Value
    57  		} else if arg.ArgType == gauge.Dynamic {
    58  			var resolvedArg *gauge.StepArg
    59  			var err error
    60  			if parent != nil {
    61  				resolvedArg, err = parent.GetArg(arg.Value)
    62  			} else {
    63  				resolvedArg, err = lookup.GetArg(arg.Value)
    64  			}
    65  			if err != nil {
    66  				return nil, err
    67  			}
    68  			//In case a special table used in a concept, you will get a dynamic table value which has to be resolved from the concept lookup
    69  			parameter.Name = resolvedArg.Name
    70  			if resolvedArg.Table.IsInitialized() {
    71  				parameter.ParameterType = gauge_messages.Parameter_Special_Table
    72  				table, err := createProtoStepTable(&resolvedArg.Table, lookup)
    73  				if err != nil {
    74  					return nil, err
    75  				}
    76  				parameter.Table = table
    77  			} else {
    78  				parameter.ParameterType = gauge_messages.Parameter_Dynamic
    79  				parameter.Value = resolvedArg.Value
    80  			}
    81  		} else if arg.ArgType == gauge.SpecialString {
    82  			parameter.ParameterType = gauge_messages.Parameter_Special_String
    83  			parameter.Value = arg.Value
    84  		} else if arg.ArgType == gauge.SpecialTable {
    85  			parameter.ParameterType = gauge_messages.Parameter_Special_Table
    86  			table, err := createProtoStepTable(&arg.Table, lookup)
    87  			if err != nil {
    88  				return nil, err
    89  			}
    90  			parameter.Table = table
    91  		} else {
    92  			parameter.ParameterType = gauge_messages.Parameter_Table
    93  			table, err := createProtoStepTable(&arg.Table, lookup)
    94  			if err != nil {
    95  				return nil, err
    96  			}
    97  			parameter.Table = table
    98  		}
    99  		parameters = append(parameters, parameter)
   100  	}
   101  
   102  	return parameters, nil
   103  }
   104  
   105  func createProtoStepTable(table *gauge.Table, lookup *gauge.ArgLookup) (*gauge_messages.ProtoTable, error) {
   106  	protoTable := new(gauge_messages.ProtoTable)
   107  	protoTable.Headers = &gauge_messages.ProtoTableRow{Cells: table.Headers}
   108  	tableRows := make([]*gauge_messages.ProtoTableRow, 0)
   109  	if len(table.Columns) == 0 {
   110  		protoTable.Rows = tableRows
   111  		return protoTable, nil
   112  	}
   113  	for i := 0; i < len(table.Columns[0]); i++ {
   114  		row := make([]string, 0)
   115  		for _, header := range table.Headers {
   116  			tableCells, _ := table.Get(header)
   117  			value := tableCells[i].Value
   118  			if tableCells[i].CellType == gauge.Dynamic {
   119  				//if concept has a table with dynamic cell, fetch from datatable
   120  				arg, err := lookup.GetArg(tableCells[i].Value)
   121  				if err != nil {
   122  					return nil, err
   123  				}
   124  				value = arg.Value
   125  			} else if tableCells[i].CellType == gauge.SpecialString {
   126  				resolvedArg, _ := newSpecialTypeResolver().resolve(value)
   127  				value = resolvedArg.Value
   128  			}
   129  			row = append(row, value)
   130  		}
   131  		tableRows = append(tableRows, &gauge_messages.ProtoTableRow{Cells: row})
   132  	}
   133  	protoTable.Rows = tableRows
   134  	return protoTable, nil
   135  }
   136  
   137  func newSpecialTypeResolver() *specialTypeResolver {
   138  	resolver := new(specialTypeResolver)
   139  	resolver.predefinedResolvers = initializePredefinedResolvers()
   140  	return resolver
   141  }
   142  
   143  func initializePredefinedResolvers() map[string]resolverFn {
   144  	return map[string]resolverFn{
   145  		"file": func(filePath string) (*gauge.StepArg, error) {
   146  			fileContent, err := util.GetFileContents(filePath)
   147  			if err != nil {
   148  				return nil, err
   149  			}
   150  			return &gauge.StepArg{Value: fileContent, ArgType: gauge.SpecialString}, nil
   151  		},
   152  		"table": func(filePath string) (*gauge.StepArg, error) {
   153  			csv, err := util.GetFileContents(filePath)
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  			csvTable, err := convertCsvToTable(csv)
   158  			if err != nil {
   159  				return nil, err
   160  			}
   161  			return &gauge.StepArg{Table: *csvTable, ArgType: gauge.SpecialTable}, nil
   162  		},
   163  	}
   164  }
   165  
   166  func (resolver *specialTypeResolver) resolve(arg string) (*gauge.StepArg, error) {
   167  	if util.IsWindows() {
   168  		arg = GetUnescapedString(arg)
   169  	}
   170  	regEx := regexp.MustCompile("(.*?):(.*)")
   171  	match := regEx.FindAllStringSubmatch(arg, -1)
   172  	specialType := strings.TrimSpace(match[0][1])
   173  	value := strings.TrimSpace(match[0][2])
   174  	stepArg, err := resolver.getStepArg(specialType, value, arg)
   175  	if err == nil {
   176  		stepArg.Name = arg
   177  	}
   178  	return stepArg, err
   179  }
   180  
   181  func (resolver *specialTypeResolver) getStepArg(specialType string, value string, arg string) (*gauge.StepArg, error) {
   182  	resolveFunc, found := resolver.predefinedResolvers[specialType]
   183  	if found {
   184  		return resolveFunc(value)
   185  	}
   186  	return nil, invalidSpecialParamError{message: fmt.Sprintf("Resolver not found for special param <%s>", arg)}
   187  }
   188  
   189  // PopulateConceptDynamicParams creates a copy of the lookup and populates table values
   190  func PopulateConceptDynamicParams(concept *gauge.Step, dataTableLookup *gauge.ArgLookup) error {
   191  	//If it is a top level concept
   192  	lookup, err := concept.Lookup.GetCopy()
   193  	if err != nil {
   194  		return err
   195  	}
   196  	for key := range lookup.ParamIndexMap {
   197  		conceptLookupArg, err := lookup.GetArg(key)
   198  		if err != nil {
   199  			return err
   200  		}
   201  		if conceptLookupArg.ArgType == gauge.Dynamic {
   202  			resolvedArg, err := dataTableLookup.GetArg(conceptLookupArg.Value)
   203  			if err != nil {
   204  				return err
   205  			}
   206  			if err = lookup.AddArgValue(key, resolvedArg); err != nil {
   207  				return err
   208  			}
   209  		}
   210  		if conceptLookupArg.ArgType == gauge.TableArg {
   211  			var updateColumns [][]gauge.TableCell
   212  			for _, cells := range conceptLookupArg.Table.Columns {
   213  				var updateCells []gauge.TableCell
   214  				for _, cell := range cells {
   215  					if cell.CellType == gauge.Dynamic {
   216  						v, err := dataTableLookup.GetArg(cell.Value)
   217  						if err != nil {
   218  							return err
   219  						}
   220  						updateCells = append(updateCells, gauge.TableCell{CellType: gauge.Static, Value: v.Value})
   221  					} else {
   222  						updateCells = append(updateCells, cell)
   223  					}
   224  				}
   225  				updateColumns = append(updateColumns, updateCells)
   226  			}
   227  			conceptLookupArg.Table.Columns = updateColumns
   228  		}
   229  	}
   230  	concept.Lookup = *lookup
   231  	//Updating values inside the concept step as well
   232  	newArgs := make([]*gauge.StepArg, 0)
   233  	for _, arg := range concept.Args {
   234  		if arg.ArgType == gauge.Dynamic {
   235  			if concept.Parent != nil {
   236  				cArg, err := concept.Parent.GetArg(arg.Value)
   237  				if err != nil {
   238  					return err
   239  				}
   240  				newArgs = append(newArgs, cArg)
   241  			} else {
   242  				dArg, err := dataTableLookup.GetArg(arg.Value)
   243  				if err != nil {
   244  					return err
   245  				}
   246  				newArgs = append(newArgs, dArg)
   247  			}
   248  		} else {
   249  			newArgs = append(newArgs, arg)
   250  		}
   251  	}
   252  	concept.Args = newArgs
   253  	concept.PopulateFragments()
   254  	return nil
   255  }
   256  
   257  // GetResolvedDataTablerows resolves any dynamic parameters in a table cell
   258  func GetResolvedDataTablerows(table *gauge.Table) {
   259  	for i, cells := range table.Columns {
   260  		for j, cell := range cells {
   261  			if cell.CellType == gauge.SpecialString {
   262  				resolvedArg, _ := newSpecialTypeResolver().resolve(cell.Value)
   263  				table.Columns[i][j].Value = resolvedArg.Value
   264  			}
   265  		}
   266  	}
   267  }