2023-09-23 18:28:40 +00:00
|
|
|
|
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 d’un 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
|
|
|
|
|
<...>
|
|
|
|
|
|
|
|
|
|
L’accès aux clés de la configuration se fait en préfixant la clé souhaitée par la section
|
|
|
|
|
d’appartenance suivie d’un 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 n’est 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 d’un template.
|
|
|
|
|
// Le template doit avoir la structure d’un 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é n’existe pas, aucune modification n’est 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 n’existe 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 d’entré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.
|
2024-03-04 08:20:42 +00:00
|
|
|
|
func (c *Setting) Write(w io.Writer) (out Option[error]) {
|
2023-09-23 18:28:40 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
2024-03-04 08:20:42 +00:00
|
|
|
|
|
2023-09-23 18:28:40 +00:00
|
|
|
|
buf := bufio.NewWriter(w)
|
|
|
|
|
for _, l := range c.t {
|
|
|
|
|
if _, err := buf.WriteString(l + "\n"); err != nil {
|
2024-03-04 08:20:42 +00:00
|
|
|
|
return Some(err)
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-03-04 08:20:42 +00:00
|
|
|
|
|
|
|
|
|
if err := buf.Flush(); err != nil {
|
|
|
|
|
out = Some(err)
|
|
|
|
|
}
|
|
|
|
|
return
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IniFile représente un fichier de configuration.
|
|
|
|
|
type IniFile struct {
|
|
|
|
|
*Setting
|
|
|
|
|
Path string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewFile génère un nouveau fichier de de configuration.
|
|
|
|
|
// Rien n’est inscrit sur le chemin donné et cela reste en mémoire
|
|
|
|
|
// tant que la méthode Save n’est pas appelée explicitement.
|
|
|
|
|
// Par ailleurs, le fichier dans le filepath n’est pas parsé
|
|
|
|
|
// tant que la méthode Load n’est pas appelée explicitement.
|
|
|
|
|
func NewFile(template, filepath string) *IniFile {
|
|
|
|
|
return &IniFile{
|
|
|
|
|
Setting: New(template),
|
|
|
|
|
Path: filepath,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load charge la configuration
|
2024-03-04 08:20:42 +00:00
|
|
|
|
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)
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|
2024-03-04 08:20:42 +00:00
|
|
|
|
|
|
|
|
|
return
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Save enregistre la configuration sur le disque.
|
2024-03-04 08:20:42 +00:00
|
|
|
|
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)
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|
2024-03-04 08:20:42 +00:00
|
|
|
|
|
|
|
|
|
return
|
2023-09-23 18:28:40 +00:00
|
|
|
|
}
|