1
0
Fork 0
golang-github-editorconfig-.../editorconfig.go
Daniel Baumann 2b08a89310
Adding upstream version 2.6.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-05-18 22:58:26 +02:00

310 lines
7.8 KiB
Go

package editorconfig
import (
"bytes"
"errors"
"fmt"
"io"
"os"
"strings"
"gopkg.in/ini.v1"
)
const (
// ConfigNameDefault represents the name of the configuration file.
ConfigNameDefault = ".editorconfig"
// UnsetValue is the value that unsets a preexisting variable.
UnsetValue = "unset"
)
// IndentStyle possible values.
const (
IndentStyleTab = "tab"
IndentStyleSpaces = "space"
)
// EndOfLine possible values.
const (
EndOfLineLf = "lf"
EndOfLineCr = "cr"
EndOfLineCrLf = "crlf"
)
// Charset possible values.
const (
CharsetLatin1 = "latin1"
CharsetUTF8 = "utf-8"
CharsetUTF8BOM = "utf-8-bom"
CharsetUTF16BE = "utf-16be"
CharsetUTF16LE = "utf-16le"
)
// Limit for section name.
const (
MaxSectionLength = 4096
)
// Editorconfig represents a .editorconfig file.
//
// It is composed by a "root" property, plus the definitions defined in the
// file.
type Editorconfig struct {
Root bool
Definitions []*Definition
config *Config
}
// newEditorconfig builds the configuration from an INI file.
func newEditorconfig(iniFile *ini.File) (*Editorconfig, error, error) {
editorConfig := &Editorconfig{}
var warning error
// Consider mixed-case values for true and false.
rootKey := iniFile.Section(ini.DefaultSection).Key("root")
rootKey.SetValue(strings.ToLower(rootKey.Value()))
editorConfig.Root = rootKey.MustBool(false)
for _, sectionStr := range iniFile.SectionStrings() {
if sectionStr == ini.DefaultSection || len(sectionStr) > MaxSectionLength {
continue
}
iniSection := iniFile.Section(sectionStr)
definition := &Definition{}
raw := make(map[string]string)
if err := iniSection.MapTo(&definition); err != nil {
return nil, nil, fmt.Errorf("error mapping current section: %w", err)
}
// Shallow copy all the properties
for k, v := range iniSection.KeysHash() {
raw[strings.ToLower(k)] = v
}
definition.Raw = raw
definition.Selector = sectionStr
if err := definition.normalize(); err != nil {
// Append those error(s) into the warning
warning = errors.Join(warning, err)
}
editorConfig.Definitions = append(editorConfig.Definitions, definition)
}
return editorConfig, warning, nil
}
// GetDefinitionForFilename returns a definition for the given filename.
//
// The result is a merge of the selectors that matched the file.
// The last section has preference over the priors.
func (e *Editorconfig) GetDefinitionForFilename(name string) (*Definition, error) {
def := &Definition{
Raw: make(map[string]string),
}
// The last section has preference over the priors.
for i := len(e.Definitions) - 1; i >= 0; i-- {
actualDef := e.Definitions[i]
selector := actualDef.Selector
if !strings.HasPrefix(selector, "/") {
if strings.ContainsRune(selector, '/') {
selector = "/" + selector
} else {
selector = "/**/" + selector
}
}
if !strings.HasPrefix(name, "/") {
name = "/" + name
}
ok, err := e.FnmatchCase(selector, name)
if err != nil {
return nil, err
}
if ok {
def.merge(actualDef)
}
}
return def, nil
}
// FnmatchCase calls the matcher from the config's parser or the vanilla's.
func (e *Editorconfig) FnmatchCase(selector string, filename string) (bool, error) {
if e.config != nil && e.config.Parser != nil {
ok, err := e.config.Parser.FnmatchCase(selector, filename)
if err != nil {
return ok, fmt.Errorf("filename match failed: %w", err)
}
return ok, nil
}
return FnmatchCase(selector, filename)
}
// Serialize converts the Editorconfig to a slice of bytes, containing the
// content of the file in the INI format.
func (e *Editorconfig) Serialize() ([]byte, error) {
buffer := bytes.NewBuffer(nil)
if err := e.Write(buffer); err != nil {
return nil, fmt.Errorf("cannot write into buffer: %w", err)
}
return buffer.Bytes(), nil
}
// Write writes the Editorconfig to the Writer in a compatible INI file.
func (e *Editorconfig) Write(w io.Writer) error {
iniFile := ini.Empty()
iniFile.Section(ini.DefaultSection).Comment = "https://editorconfig.org"
if e.Root {
iniFile.Section(ini.DefaultSection).Key("root").SetValue(boolToString(e.Root))
}
for _, d := range e.Definitions {
d.InsertToIniFile(iniFile)
}
if _, err := iniFile.WriteTo(w); err != nil {
return fmt.Errorf("error writing ini file: %w", err)
}
return nil
}
// Save saves the Editorconfig to a compatible INI file.
func (e *Editorconfig) Save(filename string) error {
f, err := os.OpenFile(filename, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
if err != nil {
return fmt.Errorf("cannot open file %q: %w", filename, err)
}
return e.Write(f)
}
func boolToString(b bool) string {
if b {
return "true"
}
return "false"
}
// Parse parses from a reader.
func Parse(r io.Reader) (*Editorconfig, error) {
iniFile, err := ini.Load(r)
if err != nil {
return nil, fmt.Errorf("cannot load ini file: %w", err)
}
ec, warning, err := newEditorconfig(iniFile)
if warning != nil {
err = errors.Join(warning, err)
}
return ec, err
}
// ParseGraceful parses from a reader with warnings not treated as a fatal error.
func ParseGraceful(r io.Reader) (*Editorconfig, error, error) {
iniFile, err := ini.Load(r)
if err != nil {
return nil, nil, fmt.Errorf("cannot load ini file: %w", err)
}
return newEditorconfig(iniFile)
}
// ParseBytes parses from a slice of bytes.
//
// Deprecated: use Parse instead.
func ParseBytes(data []byte) (*Editorconfig, error) {
iniFile, err := ini.Load(data)
if err != nil {
return nil, fmt.Errorf("cannot load ini file: %w", err)
}
ec, warning, err := newEditorconfig(iniFile)
if warning != nil {
err = errors.Join(warning, err)
}
return ec, err
}
// ParseFile parses from a file.
//
// Deprecated: use Parse instead.
func ParseFile(path string) (*Editorconfig, error) {
iniFile, err := ini.Load(path)
if err != nil {
return nil, fmt.Errorf("cannot load ini file: %w", err)
}
ec, warning, err := newEditorconfig(iniFile)
if warning != nil {
err = errors.Join(warning, err)
}
return ec, err
}
// GetDefinitionForFilename given a filename, searches for .editorconfig files,
// starting from the file folder, walking through the previous folders, until
// it reaches a folder with `root = true`, and returns the right editorconfig
// definition for the given file.
func GetDefinitionForFilename(filename string) (*Definition, error) {
config := new(Config)
return config.Load(filename)
}
// GetDefinitionForFilenameGraceful given a filename, searches for
// .editorconfig files, starting from the file folder, walking through the
// previous folders, until it reaches a folder with `root = true`, and returns
// the right editorconfig definition for the given file.
//
// In case of non-fatal errors, a joined errors warning is return as well.
func GetDefinitionForFilenameGraceful(filename string) (*Definition, error, error) {
config := new(Config)
return config.LoadGraceful(filename)
}
// GetDefinitionForFilenameWithConfigname given a filename and a configname,
// searches for configname files, starting from the file folder, walking
// through the previous folders, until it reaches a folder with `root = true`,
// and returns the right editorconfig definition for the given file.
func GetDefinitionForFilenameWithConfigname(filename string, configname string) (*Definition, error) {
config := &Config{
Name: configname,
}
return config.Load(filename)
}
// GetDefinitionForFilenameWithConfignameGraceful given a filename and a
// configname, searches for configname files, starting from the file folder,
// walking through the previous folders, until it reaches a folder with `root =
// true`, and returns the right editorconfig definition for the given file.
//
// In case of non-fatal errors, a joined errors warning is return as well.
func GetDefinitionForFilenameWithConfignameGraceful(filename string, configname string) (*Definition, error, error) {
config := &Config{
Name: configname,
}
return config.LoadGraceful(filename)
}