Module ini + ajout de convert.ToInteger

This commit is contained in:
Benjamin VAUDOUR 2023-09-23 20:28:40 +02:00
parent 6637db7be4
commit 0a1722dd44
2 changed files with 244 additions and 0 deletions

View File

@ -641,6 +641,10 @@ func ToUint[T UintType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToInteger[T IntegerType](src any, def ...T) T {
return toSingle(src, def...)
}
func ToFloat[T FloatType](src any, def ...T) T {
return toSingle(src, def...)
}

240
ini/ini.go Normal file
View File

@ -0,0 +1,240 @@
package ini
import (
"bufio"
"io"
"os"
"sort"
"strings"
"gitea.zaclys.com/bvaudour/gob/convert"
. "gitea.zaclys.com/bvaudour/gob/option"
)
/*
Setting représente le contenu dun fichier de configuration de la forme .ini.
Un fichier .ini a la forme suivante :
[section1]
clé1 = valeur
clé2 = valeur
; un commentaire éventuel
#un autre commentaire
[section2]
clé1 = valeur
<...>
Laccès aux clés de la configuration se fait en préfixant la clé souhaitée par la section
dappartenance suivie dun point (exemple : section1.clé1).
Les types de valeurs supportées actuellement sont :
- les chaînes de caractère
- les booléens : 0, 1, true, false
- les entiers
- les tableaux de chaînes : ce sont des chaînes séparées par des virgules
La conversion entre ces différents types se fait sans panic et aucun contrôle nest effectué sur la cohérence des données.
*/
type Setting struct {
t []string
m map[string]string
i map[string]int
}
// New crée une nouvelle configuration à partir dun template.
// Le template doit avoir la structure dun fichier .ini.
func New(template string) *Setting {
c := &Setting{
m: make(map[string]string),
i: make(map[string]int),
}
b := bufio.NewScanner(strings.NewReader(template))
var section string
i := -1
for b.Scan() {
i++
line := b.Text()
c.t = append(c.t, line)
line = strings.TrimSpace(line)
l := len(line)
// Line is comment or blank line
if l == 0 || line[0] == '#' || line[0] == ';' {
continue
}
// line is section header
if line[0] == '[' && line[l-1] == ']' {
section = line[1 : l-1]
continue
}
if idx := strings.Index(line, "="); idx > 0 {
key, value := strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:])
k := section + "." + key
c.m[k] = value
c.i[k] = i
}
}
return c
}
// Keys retourne la liste des clés de la configuration.
// Chaque clé est des la forme section.clé.
func (c *Setting) Keys() []string {
out := make([]string, 0, len(c.i))
for i := range c.i {
out = append(out, i)
}
sort.Slice(out, func(i, j int) bool {
return c.i[out[i]] < c.i[out[j]]
})
return out
}
// Position retourne le numéro de ligne dans laquelle se trouve la clé donnée.
// Les n° de ligne commencent à partir de 0.
func (c *Setting) Position(key string) (position Option[int]) {
if p, ok := c.i[key]; ok {
position = Some(p)
}
return
}
func conv(e any) string {
var s string
t := convert.TypeOf(e)
switch {
case t.Is(convert.Bool):
s = "0"
if b := convert.ToBool(e, false); b {
s = "1"
}
case t.Is(convert.Slice):
a := convert.ToSlice[string](e)
s = strings.Join(a, ",")
default:
s = convert.ToString(e, "")
}
return s
}
// Set définit la clé key à la valeur value.
// Si la clé nexiste pas, aucune modification nest effectuée et la méthode retourne false.
func (c *Setting) Set(key string, value any) (ok bool) {
if _, ok = c.m[key]; ok {
c.m[key] = conv(value)
}
return
}
// Get retourne la valeur de la clé demandée.
// Si celle-ci nexiste pas, une chaîne vide est retournée.
func (c *Setting) Get(key string) string { return c.m[key] }
// GetBool retourne la valeur de la clé demandée, convertie en booléen.
func (c *Setting) GetBool(key string) (out bool) {
out = convert.ToBool(c.Get(key), false)
return
}
// GetInt retourne la valeur de la clé demandée, convertie en entier.
func (c *Setting) GetInt(key string) (out int) {
out = convert.ToInt(c.Get(key), 0)
return
}
// GetUint retourne la valeur de la clé demandée, convertie en entier non signé.
func (c *Setting) GetUint(key string) (out uint) {
out = convert.ToUint(c.Get(key), uint(0))
return
}
// GetSlice retourne la valeur de la clé demandée, convertie en slice.
func (c *Setting) GetSlice(key string) (out []string) {
v := c.Get(key)
if len(v) > 0 {
out = strings.Split(v, ",")
}
return
}
// Read parse la configuration du descripteur dentrée.
func (c *Setting) Read(r io.Reader) {
b := bufio.NewScanner(r)
var section string
for b.Scan() {
line := b.Text()
line = strings.TrimSpace(line)
l := len(line)
// Line is comment or blank line
if l == 0 || line[0] == '#' || line[0] == ';' {
continue
}
// line is section header
if line[0] == '[' && line[l-1] == ']' {
section = line[1 : l-1]
continue
}
if i := strings.Index(line, "="); i > 0 && i < l-1 {
key, value := strings.TrimSpace(line[:i]), strings.TrimSpace(line[i+1:])
k := section + "." + key
c.m[k] = value
}
}
}
// Write écrit la configuration dans le descripteur de sortie.
func (c *Setting) Write(w io.Writer) error {
for k, v := range c.m {
l := c.t[c.i[k]]
i := strings.Index(l, "=")
c.t[c.i[k]] = l[:i+1] + " " + v
}
buf := bufio.NewWriter(w)
for _, l := range c.t {
if _, err := buf.WriteString(l + "\n"); err != nil {
return err
}
}
return buf.Flush()
}
// IniFile représente un fichier de configuration.
type IniFile struct {
*Setting
Path string
}
// NewFile génère un nouveau fichier de de configuration.
// Rien nest inscrit sur le chemin donné et cela reste en mémoire
// tant que la méthode Save nest pas appelée explicitement.
// Par ailleurs, le fichier dans le filepath nest pas parsé
// tant que la méthode Load nest pas appelée explicitement.
func NewFile(template, filepath string) *IniFile {
return &IniFile{
Setting: New(template),
Path: filepath,
}
}
// Load charge la configuration
func (cf *IniFile) Load() error {
f, err := os.Open(cf.Path)
if err != nil {
return err
}
defer f.Close()
cf.Setting.Read(f)
return nil
}
// Save enregistre la configuration sur le disque.
func (cf *IniFile) Save() error {
f, err := os.Create(cf.Path)
if err != nil {
return err
}
defer f.Close()
return cf.Setting.Write(f)
}