github.com/viant/toolbox@v0.34.5/README.md (about)

     1  # Toolbox - go utility library
     2  
     3  [![GoReportCard](https://goreportcard.com/badge/github.com/viant/toolbox)](https://goreportcard.com/report/github.com/viant/toolbox)
     4  [![GoDoc](https://godoc.org/github.com/viant/toolbox?status.svg)](https://godoc.org/github.com/viant/toolbox)
     5  
     6  This library is compatible with Go 1.8+
     7  
     8  Please refer to [`CHANGELOG.md`](CHANGELOG.md) if you encounter breaking changes.
     9  
    10  - [Motivation](#Motivation)
    11  - [Collection Utilities](#Collection-Utilities)
    12  - [Converter && Conversion Utilities](#Conversion-Utilities)
    13  - [Struct Utilities](#Struct-Utilities)
    14  - [Function Utilities](#Function-Utilities)
    15  - [Time Utilities](#TimeUtilities)
    16  - [Storage API](#storage)
    17  - [Data substitution](data/)
    18  - [Text Utilities](text/)
    19  - [ServiceRouter](#ServiceRouter)
    20  - [Decoder and Encoder](#DecoderandEncoder)
    21  - [Logger](#Logger)
    22  - [BatchLimiter](#BatchLimiter)
    23  - [AST Based FileSetInfo](#ast-based-filesetinfo)
    24  - [License](#License)
    25  - [Credits and Acknowledgements](#Credits-and-Acknowledgements)
    26  
    27  
    28  
    29  ## Motivation
    30  
    31  This library was developed as part of [Datastore Connectivity](https://github.com/viant/dsc/) and Testibility libraries: ([Assertly](https://github.com/viant/assertly),  [Datastore testing](https://github.com/viant/dsunit/), [End to end testing](https://github.com/viant/endly/)) 
    32  as a way to share utilities, and other abstractions that may be useful in other projects.
    33  
    34  
    35  <a name="Collection-Utilities"></a>
    36  
    37  ### Collection Utilities
    38  
    39  
    40  #### Iterator
    41  
    42  Example
    43  ```go
    44  	slice := []string{"a", "z", "c"}
    45  	iterator := toolbox.NewSliceIterator(slice)
    46      value := ""
    47      for iterator.HasNext() {
    48          iterator.Next(&value)
    49          ...
    50      }
    51  ```
    52  
    53  #### Slice utilities
    54  
    55  The following methods work on **any slice type.**
    56  
    57  **ProcessSlice**
    58  
    59  Example
    60  ```go
    61  	var aSlice interface{}
    62  	
    63  	toolbox.ProcessSlice(aSlice, func(item interface{}) bool {
    64      		...
    65      		return true //to continue to next element return true
    66      })
    67  	
    68  ```
    69  
    70  
    71  **ProcessSliceWithIndex**
    72  
    73  Example:
    74  ```go
    75  	var aSlice interface{}
    76  	
    77  	toolbox.ProcessSlice(aSlice, func(index int, item interface{}) bool {
    78      		...
    79      		return true //to continue to next element return true
    80      })
    81  	
    82  ```
    83  
    84  
    85  **IndexSlice**
    86  
    87  Example:
    88  ```go
    89      type Foo struct{
    90  		id int
    91  		name string
    92  	}
    93  
    94  	var aSlice = []Foo{ Foo{1, "A"}, Foo{2, "B"} }
    95  	var indexedMap = make(map[int]Foo)
    96  	
    97  	toolbox.IndexSlice(aSlice, indexedMap, func(foo Foo) int {
    98  		return foo.id
    99  	})
   100  	
   101  	
   102  ```
   103  
   104  
   105  **CopySliceElements**
   106  
   107  Example:
   108  ```go
   109     source := []interface{}{
   110     		"abc", "def", "cyz",
   111     	}
   112     	var target = make([]string, 0)
   113     	toolbox.CopySliceElements(source, &target)
   114  	
   115  	
   116  ```
   117  
   118  
   119  **FilterSliceElements**
   120  
   121  Example:
   122  ```go
   123  	source := []interface{}{
   124  		"abc", "def", "cyz","adc",
   125  	}
   126  	var target = make([]string, 0)
   127  	
   128  	toolbox.FilterSliceElements(source, func(item string) bool {
   129  		return strings.HasPrefix(item, "a") //this matches any elements starting with a
   130  	}, &target)
   131  ```
   132  
   133  
   134  **HasSliceAnyElements**
   135  
   136  Example:
   137  ```go
   138      source := []interface{}{
   139  		"abc", "def", "cyz","adc",
   140  	}
   141  	toolbox.HasSliceAnyElements(source, "cyz")
   142  ```
   143  
   144  
   145  **SliceToMap**
   146  
   147  Example:
   148  ```go
   149      var source = []Foo{ Foo{1, "A"}, Foo{2, "B"} }
   150  	var target = make(map[int]string)
   151  	toolbox.SliceToMap(source, target, func(foo Foo) int {
   152  		return foo.id
   153  	},
   154  	func(foo Foo) string {
   155  		return foo.name
   156  	})
   157  	
   158  
   159  ```
   160  
   161  
   162  **TransformSlice**
   163  
   164  Example:
   165  ```go
   166  type Product struct{ vendor, name string }
   167  	products := []Product{
   168  		Product{"Vendor1", "Product1"},
   169  		Product{"Vendor2", "Product2"},
   170  		Product{"Vendor1", "Product3"},
   171  		Product{"Vendor1", "Product4"},
   172  	}
   173  	var vendors=make([]string, 0)
   174  	toolbox.TransformSlice(products, &vendors, func(product Product) string {
   175  		return product.vendor
   176  	})
   177  ```
   178  
   179  
   180  #### Map utilities
   181  
   182  **ProcessMap**
   183  
   184  The following methods work on **any map type.**
   185  
   186  Example:
   187  ```go
   188      var aMap interface{}
   189  	toolbox.ProcessMap(aMap, func(key, value interface{}) bool {
   190      		...
   191      		return true //to continue to next element return true
   192      })
   193  
   194  ```
   195  
   196  
   197  **CopyMapEntries**
   198  
   199  Example:
   200  ```go
   201      type Foo struct{id int;name string}
   202  	
   203  	source := map[interface{}]interface{} {
   204  		1: Foo{1, "A"},
   205  		2: Foo{2, "B"},
   206  	}
   207  	var target = make   (map[int]Foo)
   208  
   209  	toolbox.CopyMapEntries(source, target)
   210  ```
   211  
   212  
   213  **MapKeysToSlice**
   214  
   215  Example:
   216  ```go
   217      aMap := map[string]int {
   218  		"abc":1,
   219  		"efg":2,
   220  	}
   221  	var keys = make([]string, 0)
   222  	toolbox.MapKeysToSlice(aMap, &keys)
   223  ```
   224  	
   225  
   226  **GroupSliceElements**
   227  
   228  Example:
   229  ```go
   230  	type Product struct{vendor,name string}
   231      	products := []Product{
   232      		Product{"Vendor1", "Product1"},
   233      		Product{"Vendor2", "Product2"},
   234      		Product{"Vendor1", "Product3"},
   235      		Product{"Vendor1", "Product4"},
   236      	}
   237      
   238      	productsByVendor := make(map[string][]Product)
   239      	toolbox.GroupSliceElements(products, productsByVendor, func(product Product) string {
   240      		return product.vendor
   241      	})
   242  ```
   243  
   244  
   245  **SliceToMultimap**
   246  
   247  ```go	
   248  	type Product struct {
   249      		vendor, name string
   250      		productId    int
   251      	}
   252      
   253      	products := []Product{
   254      		Product{"Vendor1", "Product1", 1},
   255      		Product{"Vendor2", "Product2", 2},
   256      		Product{"Vendor1", "Product3", 3},
   257      		Product{"Vendor1", "Product4", 4},
   258      	}
   259      
   260      	productsByVendor := make(map[string][]int)
   261      	toolbox.SliceToMultimap(products, productsByVendor, func(product Product) string {
   262      		return product.vendor
   263      	},
   264      	func(product Product) int {
   265      		return product.productId
   266      	})
   267  ```
   268  
   269  <a name="Conversion-Utilities"></a>
   270  ### Converter && Conversion Utilities
   271  
   272  Converter transforms, data between any compatible or incompatible data type including struct/basicType/map/slice/interface{}
   273  On top of that it supports custom tag to map field to target data type (i.e map)
   274  
   275  
   276  ```go
   277      myStruct :=  //some struct ...
   278      myMap := make(map[string]interface{})
   279      converter := NewConverter(dateLayout, keyTag) 	
   280      err = converter.AssignConverted(&myMap, myStruct)
   281      err = converter.AssignConverted(myStruct, myMap) 
   282  ```
   283  
   284  
   285  <a name="Struct-Utilities"></a>
   286  ### Struct Utilities
   287  
   288  
   289  **ScanStructMethods**
   290  
   291  Scan struct methods
   292  
   293  ```go
   294      service := New()
   295      err = toolbox.ScanStructMethods(service, 1, func(method reflect.Method) error {
   296  		fmt.Printf("%v\n", method.Name)
   297  		return nil
   298  	})
   299  
   300  ```
   301  
   302  **ProcessStruct**
   303  
   304  Scan struct fields
   305  
   306  ```go
   307     service := New()
   308      err = toolbox.ProcessStruct(service,
   309          func(field reflect.StructField, value reflect.Value) error {
   310              fmt.Print(field.Type.Name)
   311              return nil
   312      })
   313  
   314  ```
   315  
   316  
   317  <a name="Function-Utilities"></a>
   318  ### Function Utilities
   319  
   320  
   321  
   322  
   323  <a name="TimeUtilities"></a>
   324  ### Time Utilities	
   325  	
   326  **DateFormatToLayout**
   327  
   328  Java date format style to go date layout conversion.
   329  
   330  
   331  ```go	
   332      dateLaout := toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss z")
   333      timeValue, err := time.Parse(dateLaout, "2016-02-22 12:32:01 UTC")
   334  ```
   335  
   336  **TimeAt** 
   337  
   338  Provide dynamic semantic for creating time object
   339  
   340  ```go
   341      
   342      tomorrow, err = TimeAt("tomorrow")//tomorrow in local timezone
   343      timeInUTC, err := TimeAt("2 days ago in UTC") //or 2DayAgoInUTC
   344      yesterdayUTC, err := TimeAt("yesterdayInUTC")//yesterday in UTC
   345      hoursAhead, err := TimeAt("50 hours ahead")
   346  
   347  ```
   348  
   349  **TimeDiff**
   350  
   351  Provide dynamic semantic for creating time object based on time dif
   352  
   353  ```go
   354  
   355      lastyear, _ := time.Parse(DateFormatToLayout("yyyy-MM-dd"), "2017-01-01")
   356      ts1, e := TimeDiff(lastyear, "50 hours earlier")
   357      ts2, e := TimeDiff(lastyear, "3 days before in Poland")
   358  	
   359  ```
   360  
   361  **DayElapsed** 
   362  ```go
   363  
   364      t0, _ := time.Parse(DateFormatToLayout("yyyy-MM-dd hh:mm:ss"), "2017-01-01 12:00:00")
   365      dayElapsedInT0, err := ElapsedDay(t0) //0.5
   366  	
   367  ```
   368  
   369  **ElapsedToday**
   370  ```go
   371  
   372      elapscedInLocalTz, err := ElapsedTodayInPct("")  
   373      elapscedInUTC, err := ElapsedToday("UTC")
   374  	
   375  ```
   376   
   377  **RemainingToday**
   378  ```go
   379  
   380      elapscedInLocalTz, err := RemainingTodayInPct("")
   381      elapscedInUTC, err := RemainingToday("UTC")
   382  	
   383  ```
   384  
   385  **AtTime**
   386  ```go
   387      atTime := &AtTime{
   388          WeekDay: "*",
   389          Hour:    "*",
   390          Minute:  "10,30",
   391  	}
   392      
   393      //returns the nearest future time for xx:10 or xx:30  
   394      nextScheduleTime := atTime.Next(time.Now)
   395  ```
   396  <a name="storage"></a>
   397  
   398  ## Storage
   399  
   400  
   401  [Storage API](storage/README.md) provides unified way of accessing local or remote storage system.  
   402  
   403  This API has been deprecated, please consider using [Abstract Storage](https://github.com/viant/afs)
   404  
   405  **Example** 
   406  
   407  ```go
   408      import (
   409      	"github.com/viant/toolbox/storage"
   410      	_ "github.com/viant/toolbox/storage/gs"	
   411      )
   412      
   413      
   414      destinationURL := "gs://myBucket/set1/content.gz"
   415      destinationCredentialFile = "gs-secret.json"
   416      storageService, err := storage.NewServiceForURL(destinationURL, destinationCredentialFile)
   417  
   418  ```
   419  
   420  
   421  
   422  <a name="tet"></a>
   423  ### Text utilities
   424  
   425  
   426  **Text Case Format** 
   427  
   428  You can format with [format.Case](format/case.go).Format(text, destCase)
   429  
   430  ```go
   431      formatted := format.CaseLowerUnderscore.Format(text, format.CaseLowerCamel)
   432  ```
   433  
   434  
   435  <a name="Tokenizer"></a>
   436  ### Tokenizer
   437  
   438  <a name="ServiceRouter"></a>
   439  ### ServiceRouter
   440  
   441  This ServiceRouter provides simple WebService Endpoint abstractin and RESET Client utilities.
   442  
   443  
   444  Take as example of a ReverseService defined as follow
   445  
   446  ```go
   447  
   448  type ReverseService interface {
   449          Reverse(values []int) []int 
   450  }
   451  
   452  type reverseService struct{}
   453  
   454  func (r *reverseService) Reverse(values []int) []int {
   455  	var result = make([]int, 0)
   456  	for i := len(values) - 1; i >= 0; i-- {
   457  		result = append(result, values[i])
   458  	}
   459  
   460  	return result
   461  }
   462  
   463  ```
   464  
   465  In order to define Endpoint for this service,  define a server, a router with the service routes;
   466  
   467  
   468  
   469  ```qo
   470  
   471  
   472  type Server struct {
   473      service ReverseService
   474      port string
   475  }
   476  
   477  func (s *Server) Start() {
   478      
   479      router := toolbox.NewServiceRouter(
   480  		toolbox.ServiceRouting{
   481  			HTTPMethod: "GET",
   482  			URI:        "/v1/reverse/{ids}",
   483  			Handler:    s.service.Reverse,
   484  			Parameters: []string{"ids"}, 
   485  		},
   486  		toolbox.ServiceRouting{
   487  			HTTPMethod: "POST",
   488  			URI:        "/v1/reverse/",
   489  			Handler:    s.service.Reverse,
   490  			Parameters: []string{"ids"},
   491  		})
   492  		
   493          http.HandleFunc("/v1/", func(writer http.ResponseWriter, reader *http.Request) {
   494              err := router.Route(writer, reader)
   495              if err != nil {
   496                  response.WriteHeader(http.StatusInternalServerError)
   497              }
   498          })
   499      
   500          fmt.Printf("Started test server on port %v\n", port)
   501          log.Fatal(http.ListenAndServe(":"+port, nil))
   502  }
   503  
   504  ```
   505  **ServiceRouting** parameters define handler parameters that can be extracted from URI, QueryString, or from Post Body (json payload)
   506  In addition two special parameter names are supported: @httpRequest, @httpResponseWriter  to pass in request, and response object respectively.
   507  
   508  
   509  The REST client utility invoking our reverse service may look as follow
   510   
   511   
   512  ```go
   513  
   514                 var result = make([]int, 0)
   515                 err := toolbox.RouteToService("get", "http://127.0.0.1:8082/v1/reverse/1,7,3", nil, &result)
   516                 //...
   517                 err := toolbox.RouteToService("post", "http://127.0.0.1:8082/v1/reverse/", []int{1, 7, 3}, &result)
   518  
   519  ``` 
   520  
   521  
   522  By default a service router uses reflection to call the matched routes handler, it is possible to avoid reflection overhead by providing the custom handler invoker.
   523  
   524  ```go
   525  
   526  
   527  var ReverseInvoker = func(serviceRouting *toolbox.ServiceRouting, request *http.Request, response http.ResponseWriter, uriParameters map[string]interface{}) error {
   528  	var function = serviceRouting.Handler.(func(values []int) []int)
   529  	idsParam := uriParameters["ids"]
   530  	ids := idsParam.([]string)
   531  	values := make([]int, 0)
   532  	for _, item := range ids {
   533  		values = append(values, toolbox.AsInt(item))
   534  	}
   535  	var result = function(values)
   536  	err := toolbox.WriteServiceRoutingResponse(response, request, serviceRouting, result)
   537  	if err != nil {
   538  		return err
   539  	}
   540  	return nil
   541  }
   542  
   543  //...
   544  
   545   
   546          router := toolbox.NewServiceRouter(
   547  		toolbox.ServiceRouting{
   548  			HTTPMethod: "GET",
   549  			URI:        "/v1/reverse/{ids}",
   550  			Handler:    s.service.Reverse,
   551  			Parameters: []string{"ids"},
   552  			HandlerInvoker: ReverseInvoker,
   553  		})
   554  //...		
   555  		
   556  
   557  ```
   558  
   559  
   560  
   561  
   562  <a name="DecoderandEncoder"></a>
   563  ### Decoder and Encoder 
   564  
   565  #### Decoder
   566  
   567  This library defines DecoderFactory interface to delegate  decoder creation, 
   568  This library comes with standard JSON and UnMarshaler (protobuf) factory implementation.
   569  
   570   Example
   571    
   572  ```go
   573      factory :=toolbox.NewJsonDecoderFactory()
   574      ....
   575      
   576      decoder := factory.Create(reader)
   577      foo := &Foo{}
   578      err = decoder.Decode(foo)
   579  
   580  
   581  
   582      marshalerFactory := toolbox.NewUnMarshalerDecoderFactory()
   583      decoder := marshalerFactory.Create(reader)
   584      foo := &Foo{}
   585      err = decoder.Decode(foo)
   586  ```
   587  
   588  
   589  #### Encoder
   590  
   591  This library defines EncoderFactory interface to delegate encoder creation, 
   592  This library comes with standard JSON and Marshaler (protobuf) factory implementation.
   593  
   594   Example
   595    
   596  ```go
   597          factory :=toolbox.NewJsonEncoderFactory()
   598          ....
   599          buffer := new(bytes.Buffer)
   600          
   601          
   602          decoder := factory.Create(buffer)
   603          err = decoder.Encode(foo)
   604      
   605      
   606      
   607          marshalerFactory := toolbox.NewMarshalerEncoderFactory()
   608          decoder := marshalerFactory.Create(buffer)
   609          err = decoder.Encode(foo)
   610  ```
   611  
   612  
   613  
   614  <a name="Logger"></a>
   615  ### Logger
   616  
   617  
   618  This library provides a file logger implementation that optimizes writes.
   619  Log messages are queues until max queue size or flush frequency are met.
   620  On top of that Ctrl-C also forces immediate log messages flush to disk.
   621  
   622  File template support java style time format to manage rotation on the file name level.
   623  
   624  ```go
   625      logger, err := toolbox.NewFileLogger(toolbox.FileLoggerConfig{
   626  		LogType:           "test",
   627  		FileTemplate:      "/tmp/test[yyyyMMdd-hhmm].log",
   628  		QueueFlashCount:   250,
   629  		MaxQueueSize:      500,
   630  		FlushFrequencyInMs: 2000,
   631  		MaxIddleTimeInSec: 1,
   632  	}, toolbox.FileLoggerConfig{
   633         		LogType:           "transaction",
   634         		FileTemplate:      "/tmp/transaction[yyyyMMdd-hhmm].log",
   635         		QueueFlashCount:   250,
   636         		MaxQueueSize:      500,
   637         		FlushFrequencyInMs:2000,
   638         		MaxIddleTimeInSec: 1,
   639         	},
   640  	)
   641  
   642      logger.Log(&toolbox.LogMessage{
   643          MessageType: "test",
   644          Message:     message
   645      })
   646      
   647      logger.Log(&toolbox.LogMessage{
   648              MessageType: "transaction",
   649              Message:     message
   650          })
   651  ```
   652  		
   653  <a name="BatchLimiter"></a>
   654  ### BatchLimiter
   655  
   656  
   657  This library provides a batch limiter, that enables controling number of active go routines.
   658  
   659  
   660  ```go
   661  
   662       var tasks []*Task
   663       var batchSize = 4
   664  	 limiter:= toolbox.NewBatchLimiter(batchSize, len(tasks))
   665     	 for i, _ :=  range tasks {
   666              go func(task *Task) {
   667                      limiter.Acquire()
   668                      defer limiter.Done()
   669                      task.Run();
   670          	}(tasks[i])
   671  	}
   672  	limiter.Wait()
   673  
   674  ```
   675  
   676  ### AST Based FileSetInfo 
   677  
   678  
   679  ```go
   680      pkgPath := ""
   681  	source := path.Join(pkgPath)
   682  	filesetInfo, err :=toolbox.NewFileSetInfo(source)
   683      myType := fileSetInfo.Type("MyType")
   684      fields := myType.Fields()
   685      method := myType.Receivers
   686  ``` 
   687  
   688  
   689  
   690  
   691  ## GoCover
   692  
   693  [![GoCover](https://gocover.io/github.com/viant/toolbox)](https://gocover.io/github.com/viant/toolbox)
   694  
   695  	
   696  	
   697  <a name="License"></a>
   698  ## License
   699  
   700  The source code is made available under the terms of the Apache License, Version 2, as stated in the file `LICENSE`.
   701  
   702  Individual files may be made available under their own specific license,
   703  all compatible with Apache License, Version 2. Please see individual files for details.
   704  
   705  
   706  <a name="Credits-and-Acknowledgements"></a>
   707  
   708  ##  Credits and Acknowledgements
   709  
   710  **Library Author:** Adrian Witas
   711  
   712  **Contributors:**