манифесты стали данными, заведён convy sync
- убраны комментарии из suite.toml и .conventions.toml: файл, который машина переписывает, комментарий через круг не проносит; объяснения ушли в README рядом, который suite init теперь заводит - удалена текстовая правка манифеста целиком — 520 строк ручного лексера TOML вместе со всем классом ошибок порчи данных - запись идёт из структур энкодером; ключ, которого инструмент не знает, запись останавливает, а не теряется молча - convy sync сверяет манифест и подводит под него раскладку файлов: чего не хватает — собирает, что осиротело — удаляет, копию с локальной частью не трогает никогда
This commit is contained in:
@@ -1,12 +1,24 @@
|
||||
// Package manifest reads suite.toml, the manifest of a conventions suite.
|
||||
// Package manifest reads and writes the two manifests of the model.
|
||||
//
|
||||
// The manifest declares three things: the language the suite's rules are
|
||||
// The suite manifest declares three things: the language the suite's rules are
|
||||
// written in, its live and retired topics, and its live and retired rule
|
||||
// prefixes together with the paths of their files. The tool knows no topic and
|
||||
// no prefix in advance — that whole list arrives from here.
|
||||
//
|
||||
// Both manifests are data the tool edits, so both are decoded into structs and
|
||||
// written back out of them. They carry no comments: a file a machine rewrites
|
||||
// cannot keep a comment through the round trip, and pretending otherwise costs
|
||||
// the comment on a day nobody is watching. What a topic is for is said in the
|
||||
// documents next to the manifest, which no command touches.
|
||||
//
|
||||
// Because a write goes out of the structs, a key the tool does not know would
|
||||
// disappear on the next edit. So it does not write at all while one is there:
|
||||
// a refusal naming the key is the only outcome that neither loses it nor hides
|
||||
// it.
|
||||
package manifest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -38,25 +50,44 @@ const DefaultLanguageCode = "ru"
|
||||
// changing — the vocabulary is picked by version and code either way.
|
||||
type Language struct {
|
||||
Version int `toml:"version"`
|
||||
Lang string `toml:"lang"`
|
||||
Source string `toml:"source"`
|
||||
Description string `toml:"description"`
|
||||
Reading string `toml:"reading"`
|
||||
Lang string `toml:"lang,omitempty"`
|
||||
Source string `toml:"source,omitempty"`
|
||||
Description string `toml:"description,omitempty"`
|
||||
Reading string `toml:"reading,omitempty"`
|
||||
}
|
||||
|
||||
// Section is a part of the manifest split into a live and a retired half.
|
||||
// Retired entries are kept rather than deleted: a topic name and a rule prefix
|
||||
// live on in foreign repositories, and neither may ever be reused.
|
||||
type Section struct {
|
||||
Live map[string]string `toml:"live"`
|
||||
Retired map[string]string `toml:"retired"`
|
||||
Live map[string]string `toml:"live,omitempty"`
|
||||
Retired map[string]string `toml:"retired,omitempty"`
|
||||
}
|
||||
|
||||
// Add puts an entry into the live half, making the map if there is none.
|
||||
func (s *Section) Add(key, value string) {
|
||||
if s.Live == nil {
|
||||
s.Live = make(map[string]string)
|
||||
}
|
||||
s.Live[key] = value
|
||||
}
|
||||
|
||||
// Retire moves an entry out of the live half into the retired one. A name is
|
||||
// never deleted and never reissued: it lives on in foreign repositories, and a
|
||||
// name handed out twice starts pointing at something else there.
|
||||
func (s *Section) Retire(key, note string) {
|
||||
delete(s.Live, key)
|
||||
if s.Retired == nil {
|
||||
s.Retired = make(map[string]string)
|
||||
}
|
||||
s.Retired[key] = note
|
||||
}
|
||||
|
||||
// Manifest is a parsed suite.toml.
|
||||
type Manifest struct {
|
||||
Language Language `toml:"language"`
|
||||
Topics Section `toml:"topics"`
|
||||
Prefixes Section `toml:"prefixes"`
|
||||
Topics Section `toml:"topics,omitempty"`
|
||||
Prefixes Section `toml:"prefixes,omitempty"`
|
||||
|
||||
// Path is where the manifest was read from.
|
||||
Path string `toml:"-"`
|
||||
@@ -91,6 +122,33 @@ func Load(root string) (*Manifest, error) {
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
// Save writes the manifest back to where it was read from.
|
||||
func (m *Manifest) Save() error {
|
||||
return save(m.Path, m, m.Undecoded)
|
||||
}
|
||||
|
||||
// save encodes a manifest and puts it in place.
|
||||
func save(path string, value any, undecoded []string) error {
|
||||
if len(undecoded) > 0 {
|
||||
return fmt.Errorf("%s holds %s the tool does not know (%s); a write goes out of what the tool understands, so the key would be dropped — fix the spelling first",
|
||||
path, plural(len(undecoded), "key"), strings.Join(undecoded, ", "))
|
||||
}
|
||||
var b bytes.Buffer
|
||||
enc := toml.NewEncoder(&b)
|
||||
enc.Indent = ""
|
||||
if err := enc.Encode(value); err != nil {
|
||||
return fmt.Errorf("encoding %s: %w", path, err)
|
||||
}
|
||||
return os.WriteFile(path, b.Bytes(), 0o644)
|
||||
}
|
||||
|
||||
func plural(n int, noun string) string {
|
||||
if n == 1 {
|
||||
return fmt.Sprintf("%d %s", n, noun)
|
||||
}
|
||||
return fmt.Sprintf("%d %ss", n, noun)
|
||||
}
|
||||
|
||||
// Find walks up from start looking for a directory that holds a suite
|
||||
// manifest, so that `convy suite check` works from any subdirectory of a suite.
|
||||
func Find(start string) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user