gob/ini/ini.go

249 lines
5.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) (out Option[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 Some(err)
}
}
if err := buf.Flush(); err != nil {
out = Some(err)
}
return
}
// 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() (out Option[error]) {
if f, err := os.Open(cf.Path); err == nil {
defer f.Close()
cf.Setting.Read(f)
} else {
out = Some(err)
}
return
}
// Save enregistre la configuration sur le disque.
func (cf *IniFile) Save() (out Option[error]) {
if f, err := os.Create(cf.Path); err == nil {
defer f.Close()
out = cf.Setting.Write(f)
} else {
out = Some(err)
}
return
}