aboutsummaryrefslogtreecommitdiffstats
path: root/models/repo/search.go
blob: 4d64acf8cfe37b5631a2015d6fc7be1ff3236eb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package repo

import "code.gitea.io/gitea/models/db"

// SearchOrderByMap represents all possible search order
var SearchOrderByMap = map[string]map[string]db.SearchOrderBy{
	"asc": {
		"alpha":   db.SearchOrderByAlphabetically,
		"created": db.SearchOrderByOldest,
		"updated": db.SearchOrderByLeastUpdated,
		"size":    db.SearchOrderBySize,
		"id":      db.SearchOrderByID,
	},
	"desc": {
		"alpha":   db.SearchOrderByAlphabeticallyReverse,
		"created": db.SearchOrderByNewest,
		"updated": db.SearchOrderByRecentUpdated,
		"size":    db.SearchOrderBySizeReverse,
		"id":      db.SearchOrderByIDReverse,
	},
}
nenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
package editorconfig

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"runtime"
	"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"
	CharsetUTF16BE = "utf-16be"
	CharsetUTF16LE = "utf-16le"
	CharsetUTF8BOM = "utf-8 bom"
)

// 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) {
	editorConfig := &Editorconfig{}

	// 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, 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 {
			return nil, fmt.Errorf("normalization error: %w", err)
		}

		editorConfig.Definitions = append(editorConfig.Definitions, definition)
	}

	return editorConfig, 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, "/") {
			if runtime.GOOS != "windows" {
				name = "/" + name
			} else {
				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 {
		return e.config.Parser.FnmatchCase(selector, filename)
	}

	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)

	err := e.Write(buffer)
	if 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)
	}

	_, err := iniFile.WriteTo(w)
	if 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)
	}

	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)
	}

	return newEditorconfig(iniFile)
}

// 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)
	}

	return newEditorconfig(iniFile)
}

// 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)
}

// 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)
}