github.com/mhlo/force@v0.22.28-0.20150915022417-6d05ecfb0b47/display.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"sort"
     8  	"strings"
     9  )
    10  
    11  var BatchInfoTemplate = `
    12  Id 			%s
    13  JobId 			%s
    14  State 			%s
    15  CreatedDate 		%s
    16  SystemModstamp 		%s
    17  NumberRecordsProcessed  %d
    18  `
    19  
    20  type ByXmlName []DescribeMetadataObject
    21  
    22  func (a ByXmlName) Len() int           { return len(a) }
    23  func (a ByXmlName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    24  func (a ByXmlName) Less(i, j int) bool { return a[i].XmlName < a[j].XmlName }
    25  
    26  type ByFullName []MDFileProperties
    27  
    28  func (a ByFullName) Len() int           { return len(a) }
    29  func (a ByFullName) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
    30  func (a ByFullName) Less(i, j int) bool { return a[i].FullName < a[j].FullName }
    31  
    32  func DisplayListMetadataResponse(resp ListMetadataResponse) {
    33  	sort.Sort(ByFullName(resp.Result))
    34  	for _, result := range resp.Result {
    35  		fmt.Println(result.FullName + " - " + result.Type)
    36  	}
    37  }
    38  
    39  func DisplayListMetadataResponseJson(resp ListMetadataResponse) {
    40  	sort.Sort(ByFullName(resp.Result))
    41  	b, err := json.MarshalIndent(resp.Result, "", "   ")
    42  	if err != nil {
    43  		ErrorAndExit(err.Error())
    44  	}
    45  	fmt.Printf("%s\n", string(b))
    46  }
    47  
    48  func DisplayMetadataList(metadataObjects []DescribeMetadataObject) {
    49  
    50  	sort.Sort(ByXmlName(metadataObjects))
    51  
    52  	for _, obj := range metadataObjects {
    53  		fmt.Printf("%s ==> %s\n", obj.XmlName, obj.DirectoryName)
    54  		if len(obj.ChildXmlNames) > 0 {
    55  			sort.Strings(obj.ChildXmlNames)
    56  			for _, child := range obj.ChildXmlNames {
    57  				fmt.Printf("\t%s\n", child)
    58  			}
    59  		}
    60  	}
    61  }
    62  
    63  func DisplayMetadataListJson(metadataObjects []DescribeMetadataObject) {
    64  
    65  	sort.Sort(ByXmlName(metadataObjects))
    66  
    67  	for _, obj := range metadataObjects {
    68  		if len(obj.ChildXmlNames) > 0 {
    69  			sort.Strings(obj.ChildXmlNames)
    70  		}
    71  	}
    72  
    73  	b, err := json.MarshalIndent(metadataObjects, "", "   ")
    74  	if err != nil {
    75  		ErrorAndExit(err.Error())
    76  	}
    77  	fmt.Printf("%s\n", string(b))
    78  }
    79  
    80  func DisplayBatchList(batchInfos []BatchInfo) {
    81  
    82  	for i, batchInfo := range batchInfos {
    83  		fmt.Printf("Batch %d", i)
    84  		DisplayBatchInfo(batchInfo)
    85  		fmt.Println()
    86  	}
    87  }
    88  
    89  func DisplayBatchInfo(batchInfo BatchInfo) {
    90  
    91  	fmt.Printf(BatchInfoTemplate, batchInfo.Id, batchInfo.JobId, batchInfo.State,
    92  		batchInfo.CreatedDate, batchInfo.SystemModstamp,
    93  		batchInfo.NumberRecordsProcessed)
    94  }
    95  
    96  func DisplayJobInfo(jobInfo JobInfo) {
    97  	var msg = `
    98  Id				%s
    99  State 				%s
   100  Operation			%s
   101  Object 				%s
   102  Api Version 			%s
   103  
   104  Created By Id 			%s
   105  Created Date 			%s
   106  System Mod Stamp		%s
   107  Content Type 			%s
   108  Concurrency Mode 		%s
   109  
   110  Number Batches Queued 		%d
   111  Number Batches In Progress	%d
   112  Number Batches Completed 	%d
   113  Number Batches Failed 		%d
   114  Number Batches Total 		%d
   115  Number Records Processed 	%d
   116  Number Retries 			%d
   117  
   118  Number Records Failed 		%d
   119  Total Processing Time 		%d
   120  Api Active Processing Time 	%d
   121  Apex Processing Time 		%d
   122  `
   123  	fmt.Printf(msg, jobInfo.Id, jobInfo.State, jobInfo.Operation, jobInfo.Object, jobInfo.ApiVersion,
   124  		jobInfo.CreatedById, jobInfo.CreatedDate, jobInfo.SystemModStamp,
   125  		jobInfo.ContentType, jobInfo.ConcurrencyMode,
   126  		jobInfo.NumberBatchesQueued, jobInfo.NumberBatchesInProgress,
   127  		jobInfo.NumberBatchesCompleted, jobInfo.NumberBatchesFailed,
   128  		jobInfo.NumberBatchesTotal, jobInfo.NumberRecordsProcessed,
   129  		jobInfo.NumberRetries,
   130  		jobInfo.NumberRecordsFailed, jobInfo.TotalProcessingTime,
   131  		jobInfo.ApiActiveProcessingTime, jobInfo.ApexProcessingTime)
   132  }
   133  
   134  func DisplayForceSobjectDescribe(sobject string) {
   135  	var d interface{}
   136  	b := []byte(sobject)
   137  	err := json.Unmarshal(b, &d)
   138  	if err != nil {
   139  		ErrorAndExit(err.Error())
   140  	}
   141  	out, err := json.MarshalIndent(d, "", "    ")
   142  	fmt.Println(string(out))
   143  }
   144  
   145  func DisplayForceSobjects(sobjects []ForceSobject) {
   146  	names := make([]string, len(sobjects))
   147  	for i, sobject := range sobjects {
   148  		names[i] = sobject["name"].(string)
   149  	}
   150  	sort.Strings(names)
   151  	for _, name := range names {
   152  		fmt.Println(name)
   153  	}
   154  }
   155  
   156  func DisplayForceSobjectsJson(sobjects []ForceSobject) {
   157  	names := make([]string, len(sobjects))
   158  	for i, sobject := range sobjects {
   159  		names[i] = sobject["name"].(string)
   160  		fmt.Println(sobject)
   161  	}
   162  	b, err := json.MarshalIndent(names, "", "   ")
   163  	if err != nil {
   164  		ErrorAndExit(err.Error())
   165  	}
   166  	fmt.Printf("%s\n", string(b))
   167  }
   168  
   169  func DisplayForceRecordsf(records []ForceRecord, format string) {
   170  	switch format {
   171  	case "csv":
   172  		fmt.Println(RenderForceRecordsCSV(records, format))
   173  	case "json":
   174  		recs, _ := json.Marshal(records)
   175  		fmt.Println(string(recs))
   176  	default:
   177  		fmt.Printf("Format %s not supported\n\n", format)
   178  	}
   179  }
   180  
   181  func DisplayForceRecords(result ForceQueryResult) {
   182  	if len(result.Records) > 0 {
   183  		fmt.Print(RenderForceRecords(result.Records))
   184  	}
   185  	fmt.Println(fmt.Sprintf(" (%d records)", result.TotalSize))
   186  }
   187  
   188  func recordColumns(records []ForceRecord) (columns []string) {
   189  	for _, record := range records {
   190  		for key, _ := range record {
   191  			found := false
   192  			for _, column := range columns {
   193  				if column == key {
   194  					found = true
   195  					break
   196  				}
   197  			}
   198  			if !found {
   199  				columns = append(columns, key)
   200  			}
   201  		}
   202  	}
   203  	return
   204  }
   205  
   206  func coerceForceRecords(uncoerced []map[string]interface{}) (records []ForceRecord) {
   207  	records = make([]ForceRecord, len(uncoerced))
   208  	for i, record := range uncoerced {
   209  		records[i] = ForceRecord(record)
   210  	}
   211  	return
   212  }
   213  
   214  func columnLengths(records []ForceRecord, prefix string) (lengths map[string]int) {
   215  	lengths = make(map[string]int)
   216  
   217  	columns := recordColumns(records)
   218  	for _, column := range columns {
   219  		lengths[fmt.Sprintf("%s.%s", prefix, column)] = len(column) + 2
   220  	}
   221  
   222  	for _, record := range records {
   223  		for column, value := range record {
   224  			key := fmt.Sprintf("%s.%s", prefix, column)
   225  			length := 0
   226  			switch value := value.(type) {
   227  			case []ForceRecord:
   228  				lens := columnLengths(value, key)
   229  				for k, l := range lens {
   230  					length += l
   231  					if l > lengths[k] {
   232  						lengths[k] = l
   233  					}
   234  				}
   235  				length += len(lens) - 1
   236  			default:
   237  				if value == nil {
   238  					length = len(" (null) ")
   239  				} else {
   240  					length = len(fmt.Sprintf(" %v ", value))
   241  				}
   242  			}
   243  			if length > lengths[key] {
   244  				lengths[key] = length
   245  			}
   246  		}
   247  	}
   248  	return
   249  }
   250  
   251  func recordHeader(columns []string, lengths map[string]int, prefix string) (out string) {
   252  	headers := make([]string, len(columns))
   253  	for i, column := range columns {
   254  		key := fmt.Sprintf("%s.%s", prefix, column)
   255  		headers[i] = fmt.Sprintf(fmt.Sprintf(" %%-%ds ", lengths[key]-2), column)
   256  	}
   257  	out = strings.Join(headers, "|")
   258  	return
   259  }
   260  
   261  func recordSeparator(columns []string, lengths map[string]int, prefix string) (out string) {
   262  	separators := make([]string, len(columns))
   263  	for i, column := range columns {
   264  		key := fmt.Sprintf("%s.%s", prefix, column)
   265  		separators[i] = strings.Repeat("-", lengths[key])
   266  	}
   267  	out = strings.Join(separators, "+")
   268  	return
   269  }
   270  
   271  func recordRow(record ForceRecord, columns []string, lengths map[string]int, prefix string) (out string) {
   272  	values := make([]string, len(columns))
   273  	for i, column := range columns {
   274  		value := record[column]
   275  		switch value := value.(type) {
   276  		case []ForceRecord:
   277  			values[i] = strings.TrimSuffix(renderForceRecords(value, fmt.Sprintf("%s.%s", prefix, column), lengths), "\n")
   278  		default:
   279  			if value == nil {
   280  				values[i] = fmt.Sprintf(fmt.Sprintf(" %%-%ds ", lengths[column]-2), "(null)")
   281  			} else {
   282  				values[i] = fmt.Sprintf(fmt.Sprintf(" %%-%dv ", lengths[column]-2), value)
   283  			}
   284  		}
   285  	}
   286  	maxrows := 1
   287  	for _, value := range values {
   288  		rows := len(strings.Split(value, "\n"))
   289  		if rows > maxrows {
   290  			maxrows = rows
   291  		}
   292  	}
   293  	rows := make([]string, maxrows)
   294  	for i := 0; i < maxrows; i++ {
   295  		rowvalues := make([]string, len(columns))
   296  		for j, column := range columns {
   297  			key := fmt.Sprintf("%s.%s", prefix, column)
   298  			parts := strings.Split(values[j], "\n")
   299  			if i < len(parts) {
   300  				rowvalues[j] = fmt.Sprintf(fmt.Sprintf("%%-%ds", lengths[key]), parts[i])
   301  			} else {
   302  				rowvalues[j] = strings.Repeat(" ", lengths[key])
   303  			}
   304  		}
   305  		rows[i] = strings.Join(rowvalues, "|")
   306  	}
   307  	out = strings.Join(rows, "\n")
   308  	return
   309  }
   310  
   311  // returns first index of a given string
   312  func StringSlicePos(slice []string, value string) int {
   313  	for p, v := range slice {
   314  		if v == value {
   315  			return p
   316  		}
   317  	}
   318  	return -1
   319  } 
   320  
   321  // returns true if a slice contains given string
   322  func StringSliceContains(slice []string, value string) bool {
   323  	return StringSlicePos(slice, value) > -1
   324  }
   325  
   326  func RenderForceRecordsCSV(records []ForceRecord, format string) string {
   327  	var out bytes.Buffer
   328  
   329  	var keys []string
   330  	var flattenedRecords []map[string]interface{}
   331  	for _, record := range records {
   332  		flattenedRecord := flattenForceRecord(record)
   333  		flattenedRecords = append(flattenedRecords, flattenedRecord)
   334  		for key, _ := range flattenedRecord {
   335  			if !StringSliceContains(keys, key) {
   336  				keys = append(keys, key)
   337  			}
   338  		}
   339  	}
   340  	//keys = RemoveTransientRelationships(keys)
   341  	f, _ := ActiveCredentials()
   342  	if len(records) > 0 {
   343  		lengths := make([]int, len(keys))
   344  		outKeys := make([]string, len(keys))
   345  		for i, key := range keys {
   346  			lengths[i] = len(key)
   347  			if strings.HasSuffix(key, "__c") && f.Namespace != "" {
   348  				outKeys[i] = fmt.Sprintf(`%%%`, f.Namespace, "__", key)
   349  			} else {
   350  				outKeys[i] = key
   351  			}
   352  		}
   353  
   354  		formatter_parts := make([]string, len(outKeys))
   355  		for i, length := range lengths {
   356  			formatter_parts[i] = fmt.Sprintf(`"%%-%ds"`, length)
   357  		}
   358  
   359  		formatter := strings.Join(formatter_parts, `,`)
   360  		out.WriteString(fmt.Sprintf(formatter+"\n", StringSliceToInterfaceSlice(outKeys)...))
   361  		for _, record := range flattenedRecords {
   362  			values := make([][]string, len(keys))
   363  			for i, key := range keys {
   364  				values[i] = strings.Split(fmt.Sprintf(`%v`, record[key]), `\n`)
   365  			}
   366  
   367  			maxLines := 0
   368  			for _, value := range values {
   369  				lines := len(value)
   370  				if lines > maxLines {
   371  					maxLines = lines
   372  				}
   373  			}
   374  
   375  			for li := 0; li < maxLines; li++ {
   376  				line := make([]string, len(values))
   377  				for i, value := range values {
   378  					if len(value) > li {
   379  						line[i] = strings.Replace(value[li], `"`, `'`, -1)
   380  					}
   381  				}
   382  				out.WriteString(fmt.Sprintf(formatter+"\n", StringSliceToInterfaceSlice(line)...))
   383  			}
   384  		}
   385  	}
   386  	return out.String()
   387  	return ""
   388  }
   389  
   390  func flattenForceRecord(record ForceRecord) (flattened ForceRecord) {
   391  	flattened = make(ForceRecord)
   392  	for key, value := range record {
   393  		if key == "attributes" {
   394  			continue
   395  		}
   396  		switch value := value.(type) {
   397  		case map[string]interface{}:
   398  			if value["records"] != nil {
   399  				unflattened := value["records"].([]interface{})
   400  				subflattened := make([]ForceRecord, len(unflattened))
   401  				for i, record := range unflattened {
   402  					subflattened[i] = (map[string]interface{})(flattenForceRecord(ForceRecord(record.(map[string]interface{}))))
   403  				}
   404  				flattened[key] = subflattened
   405  			} else {
   406  				for k, v := range flattenForceRecord(value) {
   407  					flattened[fmt.Sprintf("%s.%s", key, k)] = v
   408  				}
   409  			}
   410  		default:
   411  			flattened[key] = value
   412  		}
   413  	}
   414  	return
   415  }
   416  
   417  func recordsHaveSubRows(records []ForceRecord) bool {
   418  	for _, record := range records {
   419  		for _, value := range record {
   420  			switch value := value.(type) {
   421  			case []ForceRecord:
   422  				if len(value) > 0 {
   423  					return true
   424  				}
   425  			}
   426  		}
   427  	}
   428  	return false
   429  }
   430  
   431  func renderForceRecords(records []ForceRecord, prefix string, lengths map[string]int) string {
   432  	var out bytes.Buffer
   433  
   434  	columns := recordColumns(records)
   435  
   436  	out.WriteString(recordHeader(columns, lengths, prefix) + "\n")
   437  	out.WriteString(recordSeparator(columns, lengths, prefix) + "\n")
   438  
   439  	for _, record := range records {
   440  		out.WriteString(recordRow(record, columns, lengths, prefix) + "\n")
   441  		if recordsHaveSubRows(records) {
   442  			out.WriteString(recordSeparator(columns, lengths, prefix) + "\n")
   443  		}
   444  	}
   445  
   446  	return out.String()
   447  }
   448  
   449  func RenderForceRecords(records []ForceRecord) string {
   450  	flattened := make([]ForceRecord, len(records))
   451  	for i, record := range records {
   452  		flattened[i] = flattenForceRecord(record)
   453  	}
   454  	lengths := columnLengths(flattened, "")
   455  	return renderForceRecords(flattened, "", lengths)
   456  }
   457  
   458  func DisplayForceRecord(record ForceRecord) {
   459  	DisplayInterfaceMap(record, 0)
   460  }
   461  
   462  func DisplayInterfaceMap(object map[string]interface{}, indent int) {
   463  	keys := make([]string, len(object))
   464  	i := 0
   465  	for key, _ := range object {
   466  		keys[i] = key
   467  		i++
   468  	}
   469  	sort.Strings(keys)
   470  	for _, key := range keys {
   471  		for i := 0; i < indent; i++ {
   472  			fmt.Printf("  ")
   473  		}
   474  		fmt.Printf("%s: ", key)
   475  		switch v := object[key].(type) {
   476  		case map[string]interface{}:
   477  			fmt.Printf("\n")
   478  			DisplayInterfaceMap(v, indent+1)
   479  		default:
   480  			fmt.Printf("%v\n", v)
   481  		}
   482  	}
   483  }
   484  
   485  func StringSliceToInterfaceSlice(s []string) (i []interface{}) {
   486  	for _, str := range s {
   487  		i = append(i, interface{}(str))
   488  	}
   489  	return
   490  }
   491  
   492  type ForceSobjectFields []interface{}
   493  
   494  func DisplayForceSobject(sobject ForceSobject) {
   495  	fields := ForceSobjectFields(sobject["fields"].([]interface{}))
   496  	sort.Sort(fields)
   497  	for _, f := range fields {
   498  		field := f.(map[string]interface{})
   499  		switch field["type"] {
   500  		case "picklist", "multipicklist":
   501  			var values []string
   502  			for _, value := range field["picklistValues"].([]interface{}) {
   503  				values = append(values, value.(map[string]interface{})["value"].(string))
   504  			}
   505  			fmt.Printf("%s: %s (%s)\n", field["name"], field["type"], strings.Join(values, ", "))
   506  		case "reference":
   507  			var refs []string
   508  			for _, ref := range field["referenceTo"].([]interface{}) {
   509  				refs = append(refs, ref.(string))
   510  			}
   511  			fmt.Printf("%s: %s (%s)\n", field["name"], field["type"], strings.Join(refs, ", "))
   512  		default:
   513  			fmt.Printf("%s: %s\n", field["name"], field["type"])
   514  		}
   515  	}
   516  }
   517  
   518  func DisplayFieldTypes() {
   519  	var msg = `
   520  	FIELD									 DEFAULTS
   521  	=========================================================================
   522    text/string            (length = 255)
   523    textarea               (length = 255)
   524    longtextarea           (length = 32768, visibleLines = 5)
   525    richtextarea           (length = 32768, visibleLines = 5)
   526    checkbox/bool/boolean  (defaultValue = false)
   527    datetime               ()
   528    float/double/currency  (precision = 16, scale = 2)
   529    number/int             (precision = 18, scale = 0)
   530    autonumber             (displayFormat = "AN {00000}", startingNumber = 0)
   531    geolocation            (displayLocationInDecimal = true, scale = 5)
   532    lookup                 (will be prompted for Object and label)
   533    masterdetail           (will be prompted for Object and label)
   534    picklist               ()
   535  
   536    *To create a formula field add a formula argument to the command.
   537    force field create <objectname> <fieldName>:text formula:'LOWER("HEY MAN")'
   538    `
   539  	fmt.Println(msg)
   540  }
   541  
   542  func DisplayFieldDetails(fieldType string) {
   543  	var msg = ``
   544  	switch fieldType {
   545  	case "picklist":
   546  		msg = DisplayPicklistFieldDetails()
   547  		break
   548  	case "text", "string":
   549  		msg = DisplayTextFieldDetails()
   550  		break
   551  	case "textarea":
   552  		msg = DisplayTextAreaFieldDetails()
   553  		break
   554  	case "longtextarea":
   555  		msg = DisplayLongTextAreaFieldDetails()
   556  		break
   557  	case "richtextarea":
   558  		msg = DisplayRichTextAreaFieldDetails()
   559  		break
   560  	case "checkbox", "bool", "boolean":
   561  		msg = DisplayCheckboxFieldDetails()
   562  		break
   563  	case "datetime":
   564  		msg = DisplayDatetimeFieldDetails()
   565  		break
   566  	case "float", "double", "currency":
   567  		if fieldType == "currency" {
   568  			msg = DisplayCurrencyFieldDetails()
   569  		} else {
   570  			msg = DisplayDoubleFieldDetails()
   571  		}
   572  		break
   573  	case "number", "int":
   574  		msg = DisplayDoubleFieldDetails()
   575  		break
   576  	case "autonumber":
   577  		msg = DisplayAutonumberFieldDetails()
   578  		break
   579  	case "geolocation":
   580  		msg = DisplayGeolocationFieldDetails()
   581  		break
   582  	case "lookup":
   583  		msg = DisplayLookupFieldDetails()
   584  		break
   585  	case "masterdetail":
   586  		msg = DisplayMasterDetailFieldDetails()
   587  		break
   588  	default:
   589  		msg = `
   590    Sorry, that is not a valid field type.
   591  `
   592  	}
   593  	fmt.Printf(msg + "\n")
   594  }
   595  
   596  func DisplayTextFieldDetails() (message string) {
   597  	return fmt.Sprintf(`
   598    Allows users to enter any combination of letters and numbers.
   599  
   600      %s
   601        label            - defaults to name
   602        length           - defaults to 255
   603        name
   604  
   605      %s
   606        description
   607        helptext
   608        required         - defaults to false
   609        unique           - defaults to false
   610        caseSensistive   - defaults to false
   611        externalId       - defaults to false
   612        defaultValue
   613        formula          - defaultValue must be blask
   614        formulaTreatBlanksAs  - defaults to "BlankAsZero"
   615  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   616  }
   617  
   618  func DisplayPicklistFieldDetails() (message string) {
   619  	return fmt.Sprintf(`
   620     List of options to coose from
   621  
   622      %s
   623       label            - defaults to name
   624       name
   625  
   626      %s
   627       description
   628       helptext
   629       required         - defaults to false
   630       defaultValue
   631       picklist         - comma separated list of values
   632      `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   633  }
   634  
   635  func DisplayTextAreaFieldDetails() (message string) {
   636  	return fmt.Sprintf(`
   637    Allows users to enter up to 255 characters on separate lines.
   638  
   639      %s
   640        label            - defaults to name
   641        name
   642  
   643      %s
   644        description
   645        helptext
   646        required         - defaults to false
   647        defaultValue
   648  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   649  }
   650  func DisplayLongTextAreaFieldDetails() (message string) {
   651  	return fmt.Sprintf(`
   652    Allows users to enter up to 32,768 characters on separate lines.
   653  
   654      %s
   655        label            - defaults to name
   656        length           - defaults to 32,768
   657        name
   658        visibleLines     - defaults to 3
   659  
   660      %s
   661        description
   662        helptext
   663        defaultValue
   664  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   665  }
   666  func DisplayRichTextAreaFieldDetails() (message string) {
   667  	return fmt.Sprintf(`
   668    Allows users to enter formatted text, add images and links. Up to 32,768 characters on separate lines.
   669  
   670      %s
   671        label            - defaults to name
   672        length           - defaults to 32,768
   673        name
   674        visibleLines     - defaults to 25
   675  
   676      %s
   677        description
   678        helptext
   679  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   680  }
   681  func DisplayCheckboxFieldDetails() (message string) {
   682  	return fmt.Sprintf(`
   683    Allows users to select a True (checked) or False (unchecked) value.
   684  
   685      %s
   686        label            - defaults to name
   687        name
   688  
   689      %s
   690        description
   691        helptext
   692        defaultValue     - defaults to unchecked or false
   693        formula          - defaultValue must be blask
   694        formulaTreatBlanksAs  - defaults to "BlankAsZero"
   695  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   696  }
   697  func DisplayDatetimeFieldDetails() (message string) {
   698  	return fmt.Sprintf(`
   699    Allows users to enter a date and time.
   700  
   701      %s
   702        label            - defaults to name
   703        name
   704  
   705      %s
   706        description
   707        helptext
   708        defaultValue
   709        required         - defaults to false
   710        formula          - defaultValue must be blask
   711        formulaTreatBlanksAs  - defaults to "BlankAsZero"
   712  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   713  }
   714  func DisplayDoubleFieldDetails() (message string) {
   715  	return fmt.Sprintf(`
   716    Allows users to enter any number. Leading zeros are removed.
   717  
   718      %s
   719        label            - defaults to name
   720        name
   721        precision        - digits left of decimal (defaults to 18)
   722        scale            - decimal places (defaults to 0)
   723  
   724      %s
   725        description
   726        helptext
   727        required         - defaults to false
   728        unique           - defaults to false
   729        externalId       - defaults to false
   730        defaultValue
   731        formula          - defaultValue must be blask
   732        formulaTreatBlanksAs  - defaults to "BlankAsZero"
   733  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   734  }
   735  func DisplayCurrencyFieldDetails() (message string) {
   736  	return fmt.Sprintf(`
   737    Allows users to enter a dollar or other currency amount and automatically formats the field as a currency amount.
   738  
   739      %s
   740        label            - defaults to name
   741        name
   742        precision        - digits left of decimal (defaults to 18)
   743        scale            - decimal places (defaults to 0)
   744  
   745      %s
   746        description
   747        helptext
   748        required         - defaults to false
   749        defaultValue
   750        formula          - defaultValue must be blask
   751        formulaTreatBlanksAs  - defaults to "BlankAsZero"
   752  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   753  }
   754  func DisplayAutonumberFieldDetails() (message string) {
   755  	return fmt.Sprintf(`
   756    A system-generated sequence number that uses a display format you define. The number is automatically incremented for each new record.
   757  
   758      %s
   759        label            - defaults to name
   760        name
   761        displayFormat    - defaults to "AN-{00000}"
   762        startingNumber   - defaults to 0
   763  
   764      %s
   765        description
   766        helptext
   767        externalId       - defaults to false
   768  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   769  }
   770  func DisplayGeolocationFieldDetails() (message string) {
   771  	return fmt.Sprintf(`
   772     Allows users to define locations.
   773  
   774      %s
   775        label                       - defaults to name
   776        name
   777        DisplayLocationInDecimal    - defaults false
   778        scale                       - defaults to 5 (number of decimals to the right)
   779  
   780      %s
   781        description
   782        helptext
   783        required                    - defaults to false
   784  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   785  }
   786  func DisplayLookupFieldDetails() (message string) {
   787  	return fmt.Sprintf(`
   788     Creates a relationship that links this object to another object.
   789  
   790      %s
   791        label            - defaults to name
   792        name
   793        referenceTo      - Name of related object
   794        relationshipName - defaults to referenceTo value
   795  
   796      %s
   797        description
   798        helptext
   799        required         - defaults to false
   800        relationShipLabel
   801  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   802  }
   803  func DisplayMasterDetailFieldDetails() (message string) {
   804  	return fmt.Sprintf(`
   805     Creates a special type of parent-child relationship between this object (the child, or "detail") and another object (the parent, or "master") where:
   806       The relationship field is required on all detail records.
   807       The ownership and sharing of a detail record are determined by the master record.
   808       When a user deletes the master record, all detail records are deleted.
   809       You can create rollup summary fields on the master record to summarize the detail records.
   810  
   811      %s
   812        label            - defaults to name
   813        name
   814        referenceTo      - Name of related object
   815        relationshipName - defaults to referenceTo value
   816  
   817      %s
   818        description
   819        helptext
   820        required         - defaults to false
   821        relationShipLabel
   822  `, "\x1b[31;1mrequired attributes\x1b[0m", "\x1b[31;1moptional attributes\x1b[0m")
   823  }