moteur package - github.com/Pol128/moteur - Go Packages

moteur

package module
v0.1.1 Latest Latest
Warning

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

Go to latest
Published: Aug 18, 2026 License: Apache-2.0 Imports: 13 Imported by: 0

README

moteur

Lit une ligne d'ingrédient écrite en français et rend ses parties : quantité, unité, qualificatifs, aliment, note.

pack, lexique, err := moteur.FR()
lu := moteur.Lit("2 à 3 gousses d'ail dégermées (facultatif)", pack, lexique)
// *lu.Quantite    = 2        lu.Partitif  = "d'"
// *lu.QuantiteMax = 3        lu.Aliment   = "ail dégermées"
// lu.UniteCle()   = "gousse" lu.Note      = "facultatif"
//                            lu.Optionnel = true

Le module se suffit à lui-même

Le pack de langue (lang/fr.toml) et le lexique d'aliments (data/foods_fr.json) sont embarqués avec go:embed. go get suffit : rien à récupérer ni à tenir à jour à côté.

Pour un pack à soi — une autre langue, une variante locale du lexique — Charge et ChargeAliments lisent depuis des fichiers, Lis et LisAliments depuis des octets.

Aucune règle de français n'est écrite en Go

Unités, partitifs, fractions, seuils de pluriel, formes irrégulières : tout vient du pack. Le code ne connaît pas la langue, il applique ce que le pack déclare. C'est ce qui rend une deuxième langue possible sans toucher au moteur — et ce que vérifie TestPackFactice, qui fait tourner le parser sur une langue inventée.

Mesure

go test ./...                       # 38 tests

TestJeuDeReference fait tourner le parser sur testdata/fr.txt, 287 lignes annotées à la main, tirées d'un corpus réel — fautes de frappe comprises. Le plancher d'accord est inscrit dans le fichier lui-même : il ne peut pas baisser sans que quelqu'un le change explicitement.

En ligne de commande

go install github.com/Pol128/moteur/cmd/parse@latest
parse "500 g de beurre demi-sel"              # depuis n'importe quel répertoire
parse --filtre < lignes.txt                   # TSV, pour mesurer un corpus
parse --jeu testdata/fr.txt                   # l'accord contre le jeu annoté

Le binaire lit lui aussi le pack et le lexique embarqués : rien à installer à côté. --pack et --aliments restent là pour en imposer d'autres, et --aliments "" demande explicitement de lire sans lexique.

Licence

Apache-2.0 — voir LICENSE et NOTICE. Le lexique d'aliments embarqué vient de MealieSync (MIT) ; sa mention voyage avec les données.

Documentation

Overview

Package moteur lit une ligne d'ingrédient à l'aide d'un pack de langue.

Le moteur ne connaît aucune langue : ni les unités, ni les prépositions, ni la règle d'accord ne sont écrites ici. Tout vient du pack (`lang/fr.toml`). Ajouter l'espagnol, c'est écrire `lang/es.toml` — pas toucher au Go.

Ce fichier est le portage de `crawler/langpack.py`. Le portage est volontairement littéral : à comportement identique, une divergence se lit comme une erreur de traduction et non comme une variante d'écriture.

Index

Constants

View Source
const (
	SourceAliments   = "https://github.com/Rouzax/MealieSync"
	LicenceAliments  = "MIT"
	CopyrightAliment = "Copyright (c) 2025 Rouzax"
)

SourceAliments décrit la provenance du lexique livré avec le moteur.

View Source
const Tolerance = 0.011

Tolerance : deux quantités sont les mêmes à un centième près. Le corpus écrit « 0,33 » là où le pack calcule 1/3 — exiger l'égalité stricte compterait faux une lecture juste.

Variables

View Source
var ChampsMesures = []string{"quantite", "unite", "partitif", "aliment"}

ChampsMesures sont les quatre champs sur lesquels une ligne se juge.

Functions

func BlocRestitue

func BlocRestitue(p *Pack, bloc string, lu *Ingredient) bool

BlocRestitue dit si notre découpe rend le bloc que la source publie, sans rien perdre.

La comparaison est **textuelle**, et elle ne peut pas être autre chose : relire le bloc seul avec le parser donnerait toujours un aliment et jamais une unité, puisque « une unité sans rien derrière est un aliment » — « 4 pavé(s) » isolé se lit *pavé*, aliment. C'est une règle du parser, pas un accident, et elle rend le bloc illisible hors de sa ligne.

Donc : ce que la ligne revendique comme quantité et comme unité, remis bout à bout, doit couvrir le bloc publié — ni plus, ni moins. Contrôle de frontière, pas d'exactitude.

func ExtraitNotes

func ExtraitNotes(texte string, p *Pack) (reste, note string, optionnel bool)

ExtraitNotes sort les parenthèses de la ligne : ce sont des notes, pas des aliments. Marmiton balise d'ailleurs le complément à part (« (vieilles) », « (bio) »), ce qui confirme la lecture.

Les marqueurs « facultatif » et « optionnel » sont cherchés partout, y compris dans la note qu'on vient de sortir.

func FR

func FR() (*Pack, *Lexique, error)

FR rend le couple prêt à l'emploi — c'est le point d'entrée attendu de la plupart des appelants :

pack, lexique, err := moteur.FR()
ingredient := moteur.Lit("500 g de beurre demi-sel", pack, lexique)

func Nettoie

func Nettoie(brut string) string

Nettoie ramène la ligne à sa forme lisible : espaces réduits, puce de liste retirée.

func PartitifA

func PartitifA(p *Pack, texte string, i int) string

PartitifA rend le partitif qui commence exactement à l'octet i, le plus long d'abord, tel qu'écrit dans le texte (« d' », « de la »…).

La frontière de mot est exigée des deux côtés, sinon « des » serait trouvé dans « dessert » et « de » dans « demi ».

func Span

func Span(p *Pack, aliment, note string) string

Span recolle l'aliment et sa note, débarrassés de ce qui n'est que du découpage : les parenthèses qui isolent le qualificatif chez un site et l'espace qui l'en sépare chez l'autre.

« Bœuf (carpaccio) » et « Bœuf » + « carpaccio » deviennent la même chaîne. Ce qui reste différent l'est vraiment.

func TexteQuantite

func TexteQuantite(valeur *float64) string

TexteQuantite rend une quantité sous la forme « %g » de Python, ou la chaîne vide s'il n'y en a pas.

Types

type Aliments

type Aliments interface {
	Contient(nomNormalise string) bool
}

Aliments est le lexique, quand il existe : n'importe quoi qui sache dire si un nom normalisé lui appartient.

type Desaccord

type Desaccord struct {
	Brut    string
	Attendu string
	Lu      string
}

Desaccord est une ligne que le parser ne lit pas comme l'annotateur.

func Accord

func Accord(lignes []LigneRef, p *Pack, lit func(string) *Ingredient) (float64, []Desaccord)

Accord mesure la part des lignes que le parser lit exactement comme le jeu de référence. Tout est mesuré, vides compris : un champ laissé vide y est une décision, pas une absence de publication.

type Ensemble

type Ensemble map[string]bool

Ensemble est le lexique le plus simple qui soit.

func (Ensemble) Contient

func (e Ensemble) Contient(nom string) bool

Contient satisfait Aliments.

type Ingredient

type Ingredient struct {
	Brut          string
	Motif         string
	Quantite      *float64
	QuantiteMax   *float64 // « 2 à 3 gousses »
	QuantiteTexte string
	Approximative bool // « quelques », « environ »
	Indefinie     bool // « un peu de lait »
	Unite         *Unite
	UniteTexte    string
	Qualificatifs []string
	Partitif      string
	Aliment       string
	Note          string
	Optionnel     bool
}

Ingredient est une ligne lue. Motif dit quelle règle a servi — c'est ce qui rend le résidu analysable au lieu d'être un tas d'échecs indistincts.

func Lit

func Lit(brut string, p *Pack, aliments Aliments) *Ingredient

Lit lit une ligne d'ingrédient. N'échoue jamais : une ligne illisible rend un aliment nu, ce qui est la lecture la moins fausse possible.

func (*Ingredient) EnBase

func (i *Ingredient) EnBase() *float64

EnBase rend la quantité convertie dans l'unité de base (g, ml, cm), si la conversion est possible.

func (*Ingredient) UniteCle

func (i *Ingredient) UniteCle() string

UniteCle rend la clé de l'unité, ou la chaîne vide s'il n'y en a pas.

type LectureUnite

type LectureUnite struct {
	Unite         *Unite
	Qualificatifs []string
	Facteur       float64
}

LectureUnite est ce qu'on a lu dans un texte d'unité : l'unité, et ce qui l'habillait.

Facteur porte les multiplicateurs décollés en chemin : « demi litre » rend l'unité litre et 0,5. Les qualificatifs, eux, ne changent rien à la quantité — « grosse cuillère à soupe » reste une cuillère à soupe.

type Lexique

type Lexique struct {
	Entrees int
	// contains filtered or unexported fields
}

Lexique est un ensemble de formes normalisées, et rien de plus.

func AlimentsFR

func AlimentsFR(p *Pack) (*Lexique, error)

AlimentsFR rend le lexique d'aliments embarqué, normalisé avec le pack donné.

func ChargeAliments

func ChargeAliments(chemin string, p *Pack) (*Lexique, error)

ChargeAliments lit le lexique et normalise ses formes avec le pack — sans quoi « Cœur d'artichaut » et « coeur d'artichaut » seraient deux entrées différentes et aucune ne répondrait.

func LisAliments

func LisAliments(contenu []byte, p *Pack) (*Lexique, error)

LisAliments fait le même travail depuis un lexique déjà en mémoire — celui que le module embarque, notamment.

func (*Lexique) Contient

func (l *Lexique) Contient(nom string) bool

Contient satisfait Aliments.

func (*Lexique) Formes

func (l *Lexique) Formes() int

Formes rend le nombre d'écritures reconnues.

type LigneRef

type LigneRef struct {
	Source   string
	Brut     string
	Quantite string
	Unite    string
	Partitif string
	Aliment  string
	Note     string
}

LigneRef est une ligne du jeu de référence annoté à la main.

func AnalyseJeu

func AnalyseJeu(texte string) ([]LigneRef, float64)

AnalyseJeu relit `testdata/fr.txt` : les lignes, et le plancher inscrit dans l'entête.

type Pack

type Pack struct {
	Langue                string
	Version               int
	Unites                map[string]*Unite
	ClesUnites            []string // ordre stable, pour tout ce qui énumère
	Partitifs             []string // du plus long au plus court
	PartitifsEnTete       []string
	PartitifsAccentues    bool
	Litterales            map[string]Quantite
	Fractions             map[string]Quantite
	Indefinies            []string // du plus long au plus court
	QualificatifsAvant    map[string]bool
	QualificatifsApres    map[string]bool
	Multiplicateurs       map[string]float64
	Notes                 map[string][]string
	Delimiteurs           [][2]string
	SeuilPluriel          float64
	SeparateurDecimal     string
	SeparateursIntervalle []string
	Apostrophes           []rune // la canonique en tête
	MarquesPluriel        []string
	// contains filtered or unexported fields
}

Pack est un pack de langue chargé.

func Charge

func Charge(chemin string) (*Pack, error)

Charge lit un pack depuis un fichier TOML.

func Construit

func Construit(brut map[string]any) (*Pack, error)

Construit bâtit un pack depuis la structure d'un TOML déjà lu. C'est le point d'entrée des tests : un pack factice suffit à prouver que le moteur ne connaît aucune langue.

func Lis

func Lis(contenu []byte, origine string) (*Pack, error)

Lis bâtit un pack depuis un TOML déjà en mémoire. origine ne sert qu'aux messages d'erreur : c'est ce qui permet de charger aussi bien un fichier que le pack embarqué dans le module.

func PackFR

func PackFR() (*Pack, error)

PackFR rend le pack français embarqué.

func (*Pack) Accorde

func (p *Pack) Accorde(unite *Unite, valeur *float64) string

Accorde rend « 1,5 cuillère à soupe » mais « 2 cuillères à soupe ». Le seuil vient du pack : l'anglais mettrait 1, le français met 2.

func (*Pack) EstIndefinie

func (p *Pack) EstIndefinie(texte string) bool

EstIndefinie : « un peu de lait » — il en faut, on ne dit pas combien.

func (*Pack) LireUnite

func (p *Pack) LireUnite(texte string) *LectureUnite

LireUnite rend l'unité, débarrassée de ses adjectifs.

Marmiton publie 162 « unités » dont une quarantaine ne sont qu'une unité de base habillée : « grosse cuillère à soupe », « petite boîte ». La construction est productive — on décolle plutôt que d'énumérer.

func (*Pack) LitUniteExacte

func (p *Pack) LitUniteExacte(texte string) *Unite

LitUniteExacte rend l'unité écrite exactement comme ça, sans qualificatif.

func (*Pack) Nombre

func (p *Pack) Nombre(texte string) (float64, bool)

Nombre lit « 0,5 », « 1/2 », « 1 1/2 ». Le second retour est faux si ce n'est pas un nombre.

func (*Pack) NombreDeFormes

func (p *Pack) NombreDeFormes() int

NombreDeFormes rend le nombre d'écritures d'unité reconnues, toutes unités confondues.

func (*Pack) Normalise

func (p *Pack) Normalise(texte string) string

Normalise rend comparables « Càs », « càs » et « CaS » — les trois existent dans le corpus. Sans cette étape, le pack devrait énumérer les casses, ce qui est sans fin.

plierAccents est levé pour les prépositions, et pour elles seules : « 16 dés de foies gras » ne doit pas donner le partitif « des ».

func (*Pack) Partitif

func (p *Pack) Partitif(texte string) string

Partitif rend le partitif en tête de texte, le plus long d'abord.

C'est le marqueur de frontière du français, celui que le modèle positionnel de Mealie et Tandoor range dans le nom de l'aliment.

func (*Pack) Quantite

func (p *Pack) Quantite(texte string) *Quantite

Quantite lit une quantité écrite en toutes lettres, en fraction ou en chiffres.

type Quantite

type Quantite struct {
	Valeur        float64
	Approximative bool
}

Quantite est une valeur lue, et le fait qu'elle soit donnée à la louche.

type Unite

type Unite struct {
	Cle       string
	Dimension string // masse · volume · longueur · compte · imprecise
	Singulier string
	Pluriel   string
	Abrev     string
	Formes    []string
	Base      *float64 // en grammes, millilitres ou centimètres
	Facteur   *float64 // douzaine = 12, moitié = 0,5
}

Unite est une unité du pack, avec toutes ses écritures.

func (*Unite) Convertible

func (u *Unite) Convertible() bool

Convertible dit si l'unité a une valeur de base connue. Un verre n'en a pas : lui en inventer une serait le genre de supposition que ce projet refuse.

type Verdict

type Verdict map[string]*bool

Verdict porte le jugement champ par champ. Une valeur absente signifie « non mesuré » — le champ n'a pas été publié par la source.

func Compare

func Compare(attendu LigneRef, lu *Ingredient, p *Pack, videAbsent, uniteParFamille bool) Verdict

Compare juge une ligne lue face à ce qui était attendu.

videAbsent distingue les deux références du projet : chez un site, un champ vide veut dire « non publié » et ne doit rien coûter au parser ; dans l'annotation, il veut dire « cette ligne n'a pas d'unité » et doit être mesuré. C'est le seul paramètre qui les sépare, et c'est ce qui rend les deux taux comparables.

uniteParFamille rattrape une convention de Jow, qui publie la famille et non l'unité écrite : « 80 g Bœuf » y sort avec l'unité « Kilogramme », sans que la quantité soit convertie pour autant.

func (Verdict) Tout

func (v Verdict) Tout() (juste bool, mesure bool)

Tout dit si tous les champs mesurés sont justes. Une ligne dont aucun champ n'était mesurable ne compte pas comme juste.

Directories

Path Synopsis
cmd
parse command
Commande parse — le moteur en ligne de commande.
Commande parse — le moteur en ligne de commande.

Jump to

Keyboard shortcuts

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