gowords

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 1, 2024 License: MIT Imports: 10 Imported by: 0

README

go-words

Release Go version Go Reference Test Status Coverage codecov Go Report Card

The go-words is a words table and text resource library for Golang projects.

It provides text for your application messages, alerts, translations, prompts and ...

Install

To install it:

go get -u github.com/saleh-rahimzadeh/go-words

Import:

import (
  "github.com/saleh-rahimzadeh/go-words"
  "github.com/saleh-rahimzadeh/go-words/core"
)
/* OR */
import (
  gowords "github.com/saleh-rahimzadeh/go-words"
  core    "github.com/saleh-rahimzadeh/go-words/core"
)

Source

A source is a string or a file that contains text lines, each separated by a line break.

Each line must contain a key and value pair, with the key separated from the value by a separator character.

A key must be a unique and searchable word. It can be a single word or a phrase.

A value can be a single word, a phrase, or empty.

All leading and trailing whitespace of the key and value will be trimmed and removed upon loading.

A separator is a single delimiter character that separates the key and value in each line. The default separator character is = (of rune type). Other valid characters such as |, :, ;, ,, ., ?, and @ can also be used.

A line can also be a comment or an empty line.

A comment is a line that starts with a comment character. The default comment character is # (of rune type), but other valid characters such as |, :, ;, ,, ., ?, and @ can also be used.

All comments and empty lines will be removed upon loading.

You cannot use the same character for both the separator and the comment.

Source Outline:

<key>=<value>
# comment line

Key and Value samples:

Sample Result
key1=value1 key1=value1
key2 =value2 key2=value2
key3= value3 key3=value3
key4 = value4 key4=value4
key5 = value5 key5=value5
key6 = value6 key6=value6
key7 multi words = value7 multi words key7 multi words=value7 multi words
key8 multi words = value8 multi words key8 multi words=value8 multi words
key9= key9=
key10 ERROR separator not found
= value11 ERROR key not found
# comment REMOVE
#a long long long comment REMOVE
(empty line) REMOVE

Source sample:

App = MyApp
Version = 1.0
Descriptin = An enterprise application
# Config Section
size = 100,200,300,400
left location = 10
right location = 20
load_position = top left

Suppling source in code:

// Source string
const stringSource string = `
App = MyApp
Version = 1.0
Descriptin = An enterprise application
# Config Section
size = 100,200,300,400
left location = 10
right location = 20
load_position = top left
`
// Source file
var fileSource *os.File
fileSource, err := os.Open("<path_to_string_file>")

APIs

The go-words contain 3 different types of APIs, each with a different source and storage, so they have different performances, throughput, and resource usage.

API Source Storage Source Validation Resource Usage
WordsRepository string array On instantiation Memory
WordsCollection string map On instantiation Memory
WordsFile *os.File *os.File Calling CheckError CPU

Instantiation

To create WordsRepository and WordsCollection instances use NewWordsRepository, NewWordsCollection functions and provide source, separator character and comment character.

WordsRepository example:

func main() {
  const separator = '='
  const comment = '#'
  var err error

  var wrd gowords.WordsRepository
  wrd, err = gowords.NewWordsRepository(stringSource, separator, comment)
}

WordsCollection example:

func main() {
  const separator = '='
  const comment = '#'
  var err error

  var wrd gowords.WordsCollection
  wrd, err = gowords.NewWordsCollection(stringSource, separator, comment)
}

These functions check and validate source, separator character, comment character and duplication on calling.

To create WordsFile instance use NewWordsFile function and provide source, separator character and comment character.

func main() {
  const separator = '='
  const comment = '#'
  var err error

  var fileSource *os.File
  fileSource, err = os.Open("<path_to_string_file>")
  defer fileSource.Close()

  var wrd gowords.NewWordsFile
  wrd, err = gowords.NewWordsFile(fileSource, separator, comment)
}

This function check and validate separator character and comment character on calling. To validate source and check duplication call CheckError method after instantiation.

err := wrd.CheckError()
Delimiters

You can use pre-declared characters for separator and comment delimiters of github.com/saleh-rahimzadeh/go-words/core package in instantiation.

  • core.Separator for separator.
  • core.Comment for comment.
gowords.NewWordsRepository(stringSource, core.Separator, core.Comment)
gowords.NewWordsCollection(stringSource, core.Separator, core.Comment)
gowords.NewWordsFile(fileSource, core.Separator, core.Comment)

Usage

All APIs implement Words interface. This interface has two method Get and Find to search key and return value.

  • The Get method search for a key then return value if found, else return empty string:
var value string

value = wrd.Get("App")
println(value)  // OUTPUT: "MyApp"

value = wrd.Get("uknown_key")
println(value)  // OUTPUT: ""
  • The Find method search for a key then return value and true boolean if found, else return empty string and false boolean:
var value string
var found bool

value, found = wrd.Find("App")
println(value, found)  // OUTPUT: "MyApp", true

value, found = wrd.Find("uknown_key")
println(value, found)  // OUTPUT: "", false

Both methods validate input key on calling.

Visit "github.com/saleh-rahimzadeh/go-words/blob/main/example_test.go" to see more samples.

Suffixes

Using WithSuffix API to provide categorized words table and text resource, usually for internationalization and multi language texts.

To use WithSuffix:

  1. Provide keys of source with desire suffixes.

    It's better to concate your key and suffixe with _ character.

  2. Define a variable of Suffix type from core package and instantiate WithSuffix API with a instance of Words interface (an instance of NewWordsRepository, NewWordsCollection, and NewWordsFile) and defined suffix.

  3. Using Get and Find methods of WithSuffix instance to search for a name which applied suffix.

func main() {
  const stringSource string = `
key1_EN = Value 1 English
key1_FA = Value 1 Farsi

key2_EN = Value 2 English
key2_FA = Value 2 Farsi

key3_EN = Value 3 English
key3_FA = Value 3 Farsi
`

  const EN core.Suffix = "_EN"
  const FA core.Suffix = "_FA"

  var wrd gowords.WordsRepository
  wrd, err := gowords.NewWordsRepository(stringSource, core.Separator, core.Comment)
  if err != nil {
    panic(err)
  }

  wordsEN, err := gowords.NewWithSuffix(wrd, EN)
  if err != nil {
    panic(err)
  }

  wordsFA, err := gowords.NewWithSuffix(wrd, FA)
  if err != nil {
    panic(err)
  }

  value1en := wordsEN.Get("key1")
  println(value1en)  // OUTPUT: "Value 1 English"

  value2en, found2en := wordsEN.Find("key2")
  println(value2en, found2en)  // OUTPUT: "Value 2 English", true

  value1fa := wordsFA.Get("key1")
  println(value1fa)  // OUTPUT: "Value 1 Farsi"

  value2fa, found2fa := wordsFA.Find("key2")
  println(value2fa, found2fa)  // OUTPUT: "Value 2 Farsi", true
}

The NewWithSuffix function validate suffix on calling.

Annotations

Using DoAnnotation API to format value according to an annotation or a format specifier.

There are 3 types of annotations:

  • Named: format value using named tokens like {{name}} by GetNamed and FindNamed methods.
  • Indexed: format value using indexed tokens like {{1}} by GetIndexed and FindIndexed methods.
  • Formatted: format value using formatted verbs (https://pkg.go.dev/fmt#hdr-Printing) like %s by GetFormatted and FindFormatted methods.
func main() {
  const stringSource string = `
key_named = Application {{name}} , Version {{ver}}.
key_indexed = Application {{1}} , Version {{2}}.
key_formatted = Application %s , Version %d.
`

  var wRepository gowords.WordsRepository
  wRepository, err := gowords.NewWordsRepository(stringSource, core.Separator, core.Comment)
  if err != nil {
    panic(err)
  }

  words, err := gowords.NewDoAnnotation(wRepository)
  if err != nil {
    panic(err)
  }

  value1 := words.GetNamed("key_named", map[string]string{
    "name": "MyAppX",
    "age": "111",
  })
  fmt.Println(value1)  // OUTPUT: "Application MyAppX , Version 111"

  value2, found2 := words.FindNamed("key_named", map[string]string{
    "name": "MyAppZ",
    "age": "222",
  })
  fmt.Println(value2, found2)  // OUTPUT: "Application MyAppZ , Version 222", true

  value3 := words.GetIndexed("key_indexed", "MyAppQ", 333)
  fmt.Println(value3)  // OUTPUT: "Application MyAppQ , Version 333"

  value4, found4 := words.FindIndexed("key_indexed", "MyAppW", 444)
  fmt.Println(value4, found4)  // OUTPUT: "Application MyAppW , Version 444"

  value5 := words.GetFormatted("key_formatted", "MyAppN", 555)
  fmt.Println(value5)  // OUTPUT: "Application MyAppN , Version 555"

  value6, found6 := words.FindFormatted("key_formatted", "MyAppM", 666)
  fmt.Println(value6, found6)  // OUTPUT: "Application MyAppM , Version 666"
}

Helper functions

There are some service functions, providing helper and utility functions, and also a simpler interface to working with APIs:

  • GetBy: a helper to search for a name by suffix and using Get method of Words object.
const EN core.Suffix = "_EN"
value1En := gowords.GetBy(wrd, "key1", EN)
  • FindBy: a helper to search for a name by suffix and using Find method of Words object.
const EN core.Suffix = "_EN"
value1En, found := gowords.FindBy(wrd, "key1", EN)

Internationalization and Multi-Language

To internationalization your messages, alerts and texts, leverage WithSuffix API.

Prior to version 1.1.0, visit Wiki Internationalization.

Benchmark

goos: linux
goarch: amd64
pkg: github.com/saleh-rahimzadeh/go-words
cpu: Intel(R) Core(TM) i3 CPU  @ 2.93GHz
BenchmarkWordsCollection-4            20426679        62.60 ns/op          0 B/op       0 allocs/op
BenchmarkWordsRepository-4               65107        16248 ns/op          1 B/op       0 allocs/op
BenchmarkWordsFile-4                      9357       191994 ns/op      19280 B/op    1001 allocs/op
BenchmarkWordsFileUnsafe-4                5299       238233 ns/op      19280 B/op    1001 allocs/op
BenchmarkDoAnnotationNamed-4            352963         4641 ns/op        152 B/op       6 allocs/op
BenchmarkDoAnnotationIndexed-4          171255         6257 ns/op        498 B/op       9 allocs/op
BenchmarkDoAnnotationFormatted-4       1000000         1040 ns/op         64 B/op       1 allocs/op
PASS
coverage: 46.2% of statements
ok    github.com/saleh-rahimzadeh/go-words  6.400s

Architecture Decisions

Architecture decision records (ADR) and design specifications:

Index Description
01 Deciding on a parsing strategy
02 Providing different storages

Documentation

Overview

Package gowords provides words table and text resource.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func FindBy added in v1.1.0

func FindBy(w Words, name string, suffix core.Suffix) (string, bool)

FindBy a helper to search for a name by suffix and using Words object, then return value and `true` if found, else return empty string and `false`. Also return empty string and `false` if suffix is invalid.

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const source = `
k1_EN = v1 EN
k2_EN = v2 EN
k1_FA = v1 FA
k2_FA = v2 FA
`

	var words gowords.Words
	words, err := gowords.NewWordsRepository(source, core.Separator, core.Comment)
	if err != nil {
		panic(err)
	}

	const (
		EN core.Suffix = "_EN"
		FA core.Suffix = "_FA"
	)

	valueEn, foundEn := gowords.FindBy(words, "k1", EN)
	if foundEn {
		fmt.Println(valueEn)
	}

	valueFa, foundFa := gowords.FindBy(words, "k1", FA)
	if foundFa {
		fmt.Println(valueFa)
	}

}
Output:

v1 EN
v1 FA

func GetBy added in v1.1.0

func GetBy(w Words, name string, suffix core.Suffix) string

GetBy a helper to search for a name by suffix and using Words object, then return value if found, else return empty string. Also return empty string if suffix is invalid.

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const source = `
k1_EN = v1 EN
k2_EN = v2 EN
k1_FA = v1 FA
k2_FA = v2 FA
`

	var words gowords.Words
	words, err := gowords.NewWordsRepository(source, core.Separator, core.Comment)
	if err != nil {
		panic(err)
	}

	const (
		EN core.Suffix = "_EN"
		FA core.Suffix = "_FA"
	)

	valueEn := gowords.GetBy(words, "k1", EN)
	fmt.Println(valueEn)

	valueFa := gowords.GetBy(words, "k1", FA)
	fmt.Println(valueFa)

}
Output:

v1 EN
v1 FA

Types

type DoAnnotation added in v1.1.0

type DoAnnotation struct {
	Words
}

DoAnnotation utilize Words interface to provide words table and text resource and format value according to an annotation or a format specifier

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const source = `
person_named=Hi, my name is {{name}}, when I was {{age}} years old I was a {{language}} developer.
person_indexed=Hi, my name is {{1}}, when I was {{2}} years old I was a {{3}} developer.
person_formatted=Hi, my name is %s, when I was %d years old I was a %v developer.
`

	var wRepository gowords.Words
	wRepository, err := gowords.NewWordsRepository(string(source), core.Separator, core.Comment)
	if err != nil {
		panic(err)
	}

	words, err := gowords.NewDoAnnotation(wRepository)
	if err != nil {
		panic(err)
	}

	value1person := words.GetNamed("person_named", map[string]any{
		"name":     "Saleh",
		"age":      "15",
		"language": "Assembly",
	})
	fmt.Println(value1person)

	value2person, found_person := words.FindNamed("person_named", map[string]any{
		"name":     "Saleh",
		"age":      "17",
		"language": "Pascal",
	})
	if found_person {
		fmt.Println(value2person)
	}

	value1personindexed := words.GetIndexed("person_indexed", "Saleh", "19", "C++")
	fmt.Println(value1personindexed)

	value2personindexed, found_personindexed := words.FindIndexed("person_indexed", "Saleh", "23", "CSharp")
	if found_personindexed {
		fmt.Println(value2personindexed)
	}

	value1personformatted := words.GetFormatted("person_formatted", "Saleh", 32, "JavaScript")
	fmt.Println(value1personformatted)

	value2personformatted, found_personformatted := words.FindFormatted("person_formatted", "Saleh", 36, "Golang")
	if found_personformatted {
		fmt.Println(value2personformatted)
	}

}
Output:

Hi, my name is Saleh, when I was 15 years old I was a Assembly developer.
Hi, my name is Saleh, when I was 17 years old I was a Pascal developer.
Hi, my name is Saleh, when I was 19 years old I was a C++ developer.
Hi, my name is Saleh, when I was 23 years old I was a CSharp developer.
Hi, my name is Saleh, when I was 32 years old I was a JavaScript developer.
Hi, my name is Saleh, when I was 36 years old I was a Golang developer.

func NewDoAnnotation added in v1.1.0

func NewDoAnnotation(words Words) (DoAnnotation, error)

NewDoAnnotation create a new instance of NewDoAnnotation

func (DoAnnotation) FindFormatted added in v1.1.0

func (w DoAnnotation) FindFormatted(name string, arguments ...any) (string, bool)

FindFormatted search for a name then return value and `true` if found, else return empty string and `false`. Format value with formatted verbs according to "https://pkg.go.dev/fmt#hdr-Printing".

func (DoAnnotation) FindIndexed added in v1.1.0

func (w DoAnnotation) FindIndexed(name string, arguments ...any) (string, bool)

FindIndexed search for a name then return value and `true` if found, else return empty string and `false`. Format value with indexed annotations.

func (DoAnnotation) FindNamed added in v1.1.0

func (w DoAnnotation) FindNamed(name string, arguments map[string]any) (string, bool)

FindNamed search for a name then return value and `true` if found, else return empty string and `false`. Format value with named annotations.

func (DoAnnotation) GetFormatted added in v1.1.0

func (w DoAnnotation) GetFormatted(name string, arguments ...any) string

GetNamed search for a name then return value if found, else return empty string. Format value with formatted verbs according to "https://pkg.go.dev/fmt#hdr-Printing".

func (DoAnnotation) GetIndexed added in v1.1.0

func (w DoAnnotation) GetIndexed(name string, arguments ...any) string

GetIndexed search for a name then return value if found, else return empty string. Format value with indexed annotations.

func (DoAnnotation) GetNamed added in v1.1.0

func (w DoAnnotation) GetNamed(name string, arguments map[string]any) string

GetNamed search for a name then return value if found, else return empty string. Format value with named annotations.

type WithSuffix added in v1.1.0

type WithSuffix struct {
	Words
	// contains filtered or unexported fields
}

WithSuffix utilize Words interface with suffix to provide categorized words table and text resource

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const source = `
k1_EN = v1 EN
k2_EN = v2 EN
k1_FA = v1 FA
k2_FA = v2 FA
`

	var wRepository gowords.Words
	wRepository, err := gowords.NewWordsRepository(source, core.Separator, core.Comment)
	if err != nil {
		panic(err)
	}

	const (
		EN core.Suffix = "_EN"
		FA core.Suffix = "_FA"
	)

	wordsEN, err := gowords.NewWithSuffix(wRepository, EN)
	if err != nil {
		panic(err)
	}

	wordsFA, err := gowords.NewWithSuffix(wRepository, FA)
	if err != nil {
		panic(err)
	}

	value1en := wordsEN.Get("k1")
	fmt.Println(value1en)

	value2en, found2en := wordsEN.Find("k2")
	if found2en {
		fmt.Println(value2en)
	}

	value1fa := wordsFA.Get("k1")
	fmt.Println(value1fa)

	value2fa, found2fa := wordsFA.Find("k2")
	if found2fa {
		fmt.Println(value2fa)
	}

}
Output:

v1 EN
v2 EN
v1 FA
v2 FA

func (WithSuffix) Find added in v1.1.0

func (w WithSuffix) Find(name string) (string, bool)

Find search for a name with suffix then return value and `true` if found, else return empty string and `false`

func (WithSuffix) Get added in v1.1.0

func (w WithSuffix) Get(name string) string

Get search for a name with suffix then return value if found, else return empty string

type Words

type Words interface {
	// Get search for a name then return value if found, else return empty string
	Get(string) string
	// Find search for a name then return value and `true` if found, else return empty string and `false`
	Find(string) (string, bool)
}

Words the interface to specify required methods to get and find words

func NewWithSuffix added in v1.1.0

func NewWithSuffix(words Words, suffix core.Suffix) (Words, error)

NewWordsRepository create a new instance of WithSuffix

type WordsCollection

type WordsCollection struct {
	// contains filtered or unexported fields
}

WordsCollection provide words table and text resource with accepting string source and storing in map

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
)

func main() {
	const separator rune = '='
	const comment rune = '#'
	const source = `
k1=v1
k2=v2
k3=v3
`

	w, err := gowords.NewWordsCollection(source, separator, comment)
	if err != nil {
		panic(err)
	}

	value_1, found := w.Find("k1")
	if found {
		fmt.Println(value_1)
	}

	value_2 := w.Get("k1")
	fmt.Println(value_2)

}
Output:

v1
v1

func NewWordsCollection

func NewWordsCollection(source string, separator rune, comment rune) (WordsCollection, error)

NewWordsCollection create a new instance of WordsCollection

func (WordsCollection) Find

func (w WordsCollection) Find(name string) (string, bool)

Find search for a name then return value and `true` if found, else return empty string and `false`

func (WordsCollection) Get

func (w WordsCollection) Get(name string) string

Get search for a name then return value if found, else return empty string

type WordsFile

type WordsFile struct {
	// contains filtered or unexported fields
}

WordsFile provide words table and text resource with accepting file pointer and storing a pointer to the file

Example
file, err := os.Open(path.Join(path_WORDS, "valid__want"))
if err != nil {
	panic(err)
}
defer file.Close()

const separator rune = '='
const comment rune = '#'

w, err := gowords.NewWordsFile(file, separator, comment)
if err != nil {
	panic(err)
}

err = w.CheckError()
if err != nil {
	panic(err)
}

value_1, found := w.Find("k1")
if found {
	fmt.Println(value_1)
}

var value_2 string = w.Get("k1")
fmt.Println(value_2)
Output:

v1
v1

func NewWordsFile

func NewWordsFile(file *os.File, separator rune, comment rune) (WordsFile, error)

NewWordsFile create a new instance of WordsFile

func (*WordsFile) CheckError

func (w *WordsFile) CheckError() (fault error)

CheckError check errors in file. Also check for duplication of names.

func (*WordsFile) Err

func (w *WordsFile) Err() error

Err get the error occurred in "Find" method

func (WordsFile) Find

func (w WordsFile) Find(name string) (value string, found bool)

Find search for a name then return value and `true` if found, else return empty string and `false`. It is safe for concurrent use by multiple goroutines.

func (*WordsFile) FindUnsafe

func (w *WordsFile) FindUnsafe(name string) (value string, found bool)

FindUnsafe search for a name then return value and `true` if found, else return empty string and `false`. It is unsafe for concurrent use by multiple goroutines.

func (WordsFile) Get

func (w WordsFile) Get(name string) string

Get search for a name then return value if found, else return empty string

type WordsRepository

type WordsRepository struct {
	// contains filtered or unexported fields
}

WordsRepository provide words table and text resource with accepting string source and storing in array

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
)

func main() {
	const separator rune = '='
	const comment rune = '#'
	const source = `
k1=v1
k2=v2
# comment
k3=v3
`

	w, err := gowords.NewWordsRepository(source, separator, comment)
	if err != nil {
		panic(err)
	}

	value := w.Get("k1")
	fmt.Println(value)

}
Output:

v1
Example (CustomComment)
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const comment rune = '@'
	const source string = `
k1=v1
@ this is a comment
k2=v2
`

	w, err := gowords.NewWordsRepository(source, core.Separator, comment)
	if err != nil {
		panic(err)
	}

	value := w.Get("k1")
	fmt.Println(value)

}
Output:

v1
Example (CustomSeparator)
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
	"github.com/saleh-rahimzadeh/go-words/core"
)

func main() {
	const separator rune = ':'
	const source string = `
k1:v1
k2:v2
k3:v3
`

	w, err := gowords.NewWordsRepository(source, separator, core.Comment)
	if err != nil {
		panic(err)
	}

	value := w.Get("k1")
	fmt.Println(value)

}
Output:

v1

func NewWordsRepository

func NewWordsRepository(source string, separator rune, comment rune) (WordsRepository, error)

NewWordsRepository create a new instance of WordsRepository

func (WordsRepository) Find

func (w WordsRepository) Find(name string) (string, bool)

Find search for a name then return value and `true` if found, else return empty string and `false`

Example
package main

import (
	"fmt"

	gowords "github.com/saleh-rahimzadeh/go-words"
)

func main() {
	const separator rune = '='
	const comment rune = '#'
	const source = `
k1=v1
k2=v2
k3=v3
`

	w, err := gowords.NewWordsRepository(source, separator, comment)
	if err != nil {
		panic(err)
	}

	value, found := w.Find("k1")
	if found {
		fmt.Println(value)
	}

}
Output:

v1

func (WordsRepository) Get

func (w WordsRepository) Get(name string) string

Get search for a name then return value if found, else return empty string

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL