ronantakizawa/codereview-bench huggingface dataset:
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/whyp.py (Python)

Code before:
from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
    str_or_none,
    traverse_obj,
    unified_timestamp,
    url_or_none,
)


class WhypIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?whyp\.it/tracks/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.whyp.it/tracks/18337/home-page-example-track-b4kq7',
        'md5': '02fd96427acd9547445979bf0496b013',
        'info_dict': {
            'id': '18337',
            'title': 'Example Track',
            'description': 'md5:e0b1bcf1d267dc1a0f15efff09c8f297',
            'ext': 'flac',
            'duration': 135.63,
            'uploader': 'Brad',
            'uploader_id': '1',
            'thumbnail': 'https://cdn.whyp.it/6ad0bbd9-577d-42bb-9b61-2a4f57f647eb.jpg',
        },
    }, {
        'url': 'https://www.whyp.it/tracks/18337',
        'only_matching': True,
    }]

    def _real_extract(self, url):

Review comment (suggestion):
```suggestion
    parse_iso8601,
    str_or_none,
    traverse_obj,
```

Code after:
from .common import InfoExtractor
from ..utils import (
    float_or_none,
    int_or_none,
    parse_iso8601,
    str_or_none,
    traverse_obj,
    url_or_none,
)


class WhypIE(InfoExtractor):
    _VALID_URL = r'https?://(?:www\.)?whyp\.it/tracks/(?P<id>\d+)'
    _TESTS = [{
        'url': 'https://www.whyp.it/tracks/18337/home-page-example-track-b4kq7',
        'md5': '02fd96427acd9547445979bf0496b013',
        'info_dict': {
            'id': '18337',
            'title': 'Example Track',
            'display_id': 'example-track',
            'description': 'md5:e0b1bcf1d267dc1a0f15efff09c8f297',
            'ext': 'flac',
            'duration': 135.63,
            'timestamp': 1643216583,
            'upload_date': '20220126',
            'uploader': 'Brad',
            'uploader_id': '1',
            'thumbnail': 'https://cdn.whyp.it/6ad0bbd9-577d-42bb-9b61-2a4f57f647eb.jpg',
        },
    }, {
        'url': 'https://www.whyp.it/tracks/18337',
        'only_matching': True,
Repository: yt-dlp/yt-dlp
File: yt_dlp/extractor/whyp.py (Python)

Code before:
        'url': 'https://www.whyp.it/tracks/18337',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        unique_id = self._match_id(url)
        webpage = self._download_webpage(url, unique_id)
        data = self._search_nuxt_data(webpage, unique_id)['rawTrack']

        return {
            'id': unique_id,
            'formats': [{
                'url': data[f'{prefix}_url'],
                'format_id': prefix,
                'filesize': int_or_none(data.get(f'{prefix}_size')),
                'vcodec': 'none',
                'quality': 10 if prefix == 'lossless' else -1,
                'http_headers': {'Referer': 'https://whyp.it/'},
            } for prefix in ('audio', 'lossy', 'lossless') if url_or_none(data.get(f'{prefix}_url'))],
            **traverse_obj(data, {
                'title': 'title',
                'display_id': 'slug',
                'description': 'description',
                'duration': ('duration', {float_or_none}),
                'timestamp': ('created_at', {unified_timestamp}),
                'uploader': ('user', 'username'),
                'uploader_id': ('user', 'id', {str_or_none}),
                'thumbnail': ('artwork_url', {url_or_none}),
            }),
        }


Review comment (suggestion):
```suggestion
                'uploader': ('user', 'username', {str}),
```

Code after:
            'thumbnail': 'https://cdn.whyp.it/6ad0bbd9-577d-42bb-9b61-2a4f57f647eb.jpg',
        },
    }, {
        'url': 'https://www.whyp.it/tracks/18337',
        'only_matching': True,
    }]

    def _real_extract(self, url):
        unique_id = self._match_id(url)
        webpage = self._download_webpage(url, unique_id)
        data = self._search_nuxt_data(webpage, unique_id)['rawTrack']

        return {
            'id': unique_id,
            'formats': [{
                'url': data[f'{prefix}_url'],
                'format_id': prefix,
                'filesize': int_or_none(data.get(f'{prefix}_size')),
                'vcodec': 'none',
                'quality': 10 if prefix == 'lossless' else -1,
                'http_headers': {'Referer': 'https://whyp.it/'},
            } for prefix in ('audio', 'lossy', 'lossless') if url_or_none(data.get(f'{prefix}_url'))],
            **traverse_obj(data, {
                'title': ('title', {str}),
                'display_id': ('slug', {str}),
                'description': 'description',
                'duration': ('duration', {float_or_none}),
                'timestamp': ('created_at', {parse_iso8601}),
                'uploader': ('user', 'username', {str}),
                'uploader_id': ('user', 'id', {str_or_none}),
                'thumbnail': ('artwork_url', {url_or_none}),
            }),
        }
Repository: zyedidia/micro
File: internal/buffer/cursor.go (Go)

Code before:
		c.Left()
	}
	c.Left()
	for util.IsWordChar(c.RuneUnder(c.X)) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Right()
}

// SubWordRight moves the cursor one sub-word to the right
func (c *Cursor) SubWordRight() {
	if (util.IsWhitespace(c.RuneUnder(c.X))) {
		for util.IsWhitespace(c.RuneUnder(c.X)) {
			if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
				c.Right()
				return
			}
			c.Right()
		}
		return
	}
	if (util.IsNoneWordChar(c.RuneUnder(c.X))) {
		for util.IsNoneWordChar(c.RuneUnder(c.X)) {
			if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
				c.Right()
				return
			}
			c.Right()
		}
		return
	}
	if (util.IsWordDelimiter(c.RuneUnder(c.X))) {
		for util.IsWordDelimiter(c.RuneUnder(c.X)) {
			if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
				c.Right()
				return
			}
			c.Right()
		}
		if (util.IsWhitespace(c.RuneUnder(c.X))) {
			return
		}
	}
	if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
		return
	}
	if (util.IsUpperLetter(c.RuneUnder(c.X)) &&
		util.IsUpperLetter(c.RuneUnder(c.X + 1))) {

Review comment (suggestion):
So if it is on a non-word non-whitespace character, it jumps to the nearest word or whitespace? Again, looks inconsistent with `WordRight` and `WordLeft`. But actually this seems like a reasonable behavior. But it would be reasonable for `WordRight` and `WordLeft` as well. So I'd suggest to fix `WordRight` and `WordLeft` first, and then implement `SubWordRight` and `SubWordLeft` based on that improved behavior of `WordRight` and `WordLeft`.

Code after:
		return
	}
	c.Right()
	for util.IsWordChar(c.RuneUnder(c.X)) {
		if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
			return
		}
		c.Right()
	}
}

// WordLeft moves the cursor one word to the left
func (c *Cursor) WordLeft() {
	c.Left()
	for util.IsWhitespace(c.RuneUnder(c.X)) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	if util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) &&
		util.IsNonWordChar(c.RuneUnder(c.X-1)) {
		for util.IsNonWordChar(c.RuneUnder(c.X)) && !util.IsWhitespace(c.RuneUnder(c.X)) {
			if c.X == 0 {
				return
			}
			c.Left()
		}
		c.Right()
		return
	}
	c.Left()
	for util.IsWordChar(c.RuneUnder(c.X)) {
		if c.X == 0 {
			return
		}
		c.Left()
	}
	c.Right()
}

// SubWordRight moves the cursor one sub-word to the right
func (c *Cursor) SubWordRight() {
	if util.IsWhitespace(c.RuneUnder(c.X)) {
		for util.IsWhitespace(c.RuneUnder(c.X)) {
			if c.X == util.CharacterCount(c.buf.LineBytes(c.Y)) {
				c.Right()
				return
			}
			c.Right()
		}
Repository: zyedidia/micro
File: internal/buffer/buffer.go (Go)

Code before:
			break
		}
	}

	if syntaxFile == "" {
		length := len(syntaxFiles)
		if length > 0 {
			signatureMatch := false
			if length > 1 {
				detectlimit := util.IntOpt(b.Settings["detectlimit"])
				lineCount := len(b.lines)
				limit := 0
				if detectlimit > 0 {
					if lineCount < detectlimit {
						limit = lineCount
					} else {
						limit = detectlimit
					}
				} else {
					limit = lineCount
				}
				for i := 0; i < length && !signatureMatch; i++ {
					if highlight.HasFileSignature(syntaxFiles[i].Header) {
						for j := 0; j < limit && !signatureMatch; j++ {
							if highlight.MatchFileSignature(syntaxFiles[i].Header, b.lines[j].data) {
								syntaxFile = syntaxFiles[i].FileName
								b.SyntaxDef = syntaxFiles[i].SyntaxDef
								header = syntaxFiles[i].Header
								signatureMatch = true
							}
						}
					}
				}
			}
			if length == 1 || !signatureMatch {
				syntaxFile = syntaxFiles[0].FileName
				b.SyntaxDef = syntaxFiles[0].SyntaxDef
				header = syntaxFiles[0].Header
			}
		}
	}

	if syntaxFile != "" && !foundDef {
		// we found a syntax file using a syntax header file
		for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
			if f.Name() == syntaxFile {
				data, err := f.Data()
				if err != nil {
					screen.TermMessage("Error loading syntax file " + f.Name() + ": " + err.Error())
					continue
				}

Review comment (suggestion):
Looks like it can be made much shorter:
```
limit := lineCount
if detectlimit > 0 && lineCount > detectlimit {
	limit = detectlimit
}
```

Code after:
				screen.TermMessage("Error reading syntax header file", f.Name(), err)
				continue
			}

			if ft == "unknown" || ft == "" {
				if header.MatchFileName(b.Path) {
					syntaxFiles = append(syntaxFiles, syntaxFileBuffer{header, f.Name(), nil})
				}
			} else if header.FileType == ft {
				syntaxFile = f.Name()
				break
			}
		}
	}

	if syntaxFile == "" {
		length := len(syntaxFiles)
		if length > 0 {
			signatureMatch := false
			if length > 1 {
				detectlimit := util.IntOpt(b.Settings["detectlimit"])
				lineCount := len(b.lines)
				limit := lineCount
				if detectlimit > 0 && lineCount > detectlimit {
					limit = detectlimit
				}
				for i := 0; i < length && !signatureMatch; i++ {
					if syntaxFiles[i].header.HasFileSignature() {
						for j := 0; j < limit && !signatureMatch; j++ {
							if syntaxFiles[i].header.MatchFileSignature(b.lines[j].data) {
								syntaxFile = syntaxFiles[i].fileName
								b.SyntaxDef = syntaxFiles[i].syntaxDef
								header = syntaxFiles[i].header
								signatureMatch = true
							}
						}
					}
				}
			}
			if length == 1 || !signatureMatch {
				syntaxFile = syntaxFiles[0].fileName
				b.SyntaxDef = syntaxFiles[0].syntaxDef
				header = syntaxFiles[0].header
			}
		}
	}

	if syntaxFile != "" && !foundDef {
		// we found a syntax file using a syntax header file
		for _, f := range config.ListRuntimeFiles(config.RTSyntax) {
			if f.Name() == syntaxFile {
Repository: zyedidia/micro
File: internal/buffer/eventhandler.go (Go)

Code before:

// MultipleReplace creates an multiple insertions executes them
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
	e := &TextEvent{
		C:         *eh.cursors[eh.active],
		EventType: TextEventReplace,
		Deltas:    deltas,
		Time:      time.Now(),
	}
	eh.Execute(e)
}

// Replace deletes from start to end and replaces it with the given string
func (eh *EventHandler) Replace(start, end Loc, replace string) {
	eh.Remove(start, end)
	eh.Insert(start, replace)
}

// Execute a textevent and add it to the undo stack
func (eh *EventHandler) Execute(t *TextEvent) {
	if eh.RedoStack.Len() > 0 {
		eh.RedoStack = new(TEStack)
	}
	eh.UndoStack.Push(t)

	b, err := config.RunPluginFnBool("onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
	if err != nil {
		screen.TermMessage(err)
	}

	if !b {
		return
	}

	ExecuteTextEvent(t, eh.buf)
}

// Undo the first event in the undo stack
func (eh *EventHandler) Undo() {
	t := eh.UndoStack.Peek()
	if t == nil {
		return
	}

	startTime := t.Time.UnixNano() / int64(time.Millisecond)
	endTime := startTime - (startTime % undoThreshold)

	for {
		t = eh.UndoStack.Peek()
		if t == nil {
			return

Review comment (suggestion):
Here (with the introduced `nil`) I'm currently unsure if this will work in Go, since the targeting type is passed by reference by default.

Code after:

// MultipleReplace creates an multiple insertions executes them
func (eh *EventHandler) MultipleReplace(deltas []Delta) {
	e := &TextEvent{
		C:         *eh.cursors[eh.active],
		EventType: TextEventReplace,
		Deltas:    deltas,
		Time:      time.Now(),
	}
	eh.Execute(e)
}

// Replace deletes from start to end and replaces it with the given string
func (eh *EventHandler) Replace(start, end Loc, replace string) {
	eh.Remove(start, end)
	eh.Insert(start, replace)
}

// Execute a textevent and add it to the undo stack
func (eh *EventHandler) Execute(t *TextEvent) {
	if eh.RedoStack.Len() > 0 {
		eh.RedoStack = new(TEStack)
	}
	eh.UndoStack.Push(t)

	b, err := config.RunPluginFnBool(nil, "onBeforeTextEvent", luar.New(ulua.L, eh.buf), luar.New(ulua.L, t))
	if err != nil {
		screen.TermMessage(err)
	}

	if !b {
		return
	}

	ExecuteTextEvent(t, eh.buf)
}

// Undo the first event in the undo stack
func (eh *EventHandler) Undo() {
	t := eh.UndoStack.Peek()
	if t == nil {
		return
	}

	startTime := t.Time.UnixNano() / int64(time.Millisecond)
	endTime := startTime - (startTime % undoThreshold)

	for {
		t = eh.UndoStack.Peek()
		if t == nil {
			return
Repository: zyedidia/micro
File: internal/config/colorscheme.go (Go)

Code before:
package config

import (
	"errors"
	"regexp"
	"strconv"
	"strings"

	"github.com/zyedidia/tcell/v2"
)

// DefStyle is Micro's default style
var DefStyle tcell.Style = tcell.StyleDefault

// Colorscheme is the current colorscheme
var Colorscheme map[string]tcell.Style

// ParsedColorschemes keeps track of the already parsed colorschemes
var ParsedColorschemes []string

// GetColor takes in a syntax group and returns the colorscheme's style for that group
func GetColor(color string) tcell.Style {
	st := DefStyle
	if color == "" {
		return st
	}
	groups := strings.Split(color, ".")
	if len(groups) > 1 {
		curGroup := ""
		for i, g := range groups {
			if i != 0 {
				curGroup += "."
			}
			curGroup += g
			if style, ok := Colorscheme[curGroup]; ok {
				st = style
			}
		}
	} else if style, ok := Colorscheme[color]; ok {
		st = style
	} else {
		st = StringToStyle(color)
	}


Review comment (suggestion):
It doesn't need to be publicly visible.
In fact, I think it doesn't even need to be global. You could pass it down to the recursive call as a parameter instead (unless it would unnecessarily complicate the code).

Code after:
package config

import (
	"errors"
	"regexp"
	"strconv"
	"strings"

	"github.com/zyedidia/tcell/v2"
)

// DefStyle is Micro's default style
var DefStyle tcell.Style = tcell.StyleDefault

// Colorscheme is the current colorscheme
var Colorscheme map[string]tcell.Style

// GetColor takes in a syntax group and returns the colorscheme's style for that group
func GetColor(color string) tcell.Style {
	st := DefStyle
	if color == "" {
		return st
	}
	groups := strings.Split(color, ".")
	if len(groups) > 1 {
		curGroup := ""
		for i, g := range groups {
			if i != 0 {
				curGroup += "."
			}
			curGroup += g
			if style, ok := Colorscheme[curGroup]; ok {
				st = style
			}
		}
	} else if style, ok := Colorscheme[color]; ok {
		st = style
	} else {
		st = StringToStyle(color)
	}

	return st
}
Repository: zyedidia/micro
File: internal/config/colorscheme.go (Go)

Code before:

	lines := strings.Split(text, "\n")

	c := make(map[string]tcell.Style)

lineLoop:
	for _, line := range lines {
		if strings.TrimSpace(line) == "" ||
			strings.TrimSpace(line)[0] == '#' {
			// Ignore this line
			continue
		}

		matches := includeParser.FindSubmatch([]byte(line))
		if len(matches) == 2 {
			include := string(matches[1])
			for _, name := range ParsedColorschemes {
				// check for circular includes...
				if name == include {
					// ...and prevent them
					continue lineLoop
				}
			}
			err = LoadColorscheme(include)
			if err == nil {
				c = Colorscheme
			}
			continue
		}

		matches = colorParser.FindSubmatch([]byte(line))
		if len(matches) == 3 {
			link := string(matches[1])
			colors := string(matches[2])

			style := StringToStyle(colors)
			c[link] = style

			if link == "default" {
				DefStyle = style
			}
		} else {
			err = errors.New("Color-link statement is not valid: " + line)
		}
	}

	return c, err
}

// StringToStyle returns a style from a string
// The strings must be in the format "extra foregroundcolor,backgroundcolor"

Review comment (suggestion):
So the included colorscheme cancels previous colors even if it doesn't override them?

And in general, relying on the recursive call to set the global `Colorscheme` variable, in order to assign it to a local variable and return it only to assign it again to the same global variable, feels... interesting.

Code after:
	var err error
	colorParser := regexp.MustCompile(`color-link\s+(\S*)\s+"(.*)"`)
	includeParser := regexp.MustCompile(`include\s+"(.*)"`)
	lines := strings.Split(text, "\n")
	c := make(map[string]tcell.Style)

	if parsedColorschemes != nil {
		*parsedColorschemes = append(*parsedColorschemes, name)
	}

lineLoop:
	for _, line := range lines {
		if strings.TrimSpace(line) == "" ||
			strings.TrimSpace(line)[0] == '#' {
			// Ignore this line
			continue
		}

		matches := includeParser.FindSubmatch([]byte(line))
		if len(matches) == 2 {
			// support includes only in case parsedColorschemes are given
			if parsedColorschemes != nil {
				include := string(matches[1])
				for _, name := range *parsedColorschemes {
					// check for circular includes...
					if name == include {
						// ...and prevent them
						continue lineLoop
					}
				}
				includeScheme, err := LoadColorscheme(include, parsedColorschemes)
				if err != nil {
					return c, err
				}
				for k, v := range includeScheme {
					c[k] = v
				}
			}
			continue
		}

		matches = colorParser.FindSubmatch([]byte(line))
		if len(matches) == 3 {
			link := string(matches[1])
			colors := string(matches[2])

			style := StringToStyle(colors)
			c[link] = style

			if link == "default" {
				DefStyle = style
Repository: zyedidia/micro
File: internal/buffer/buffer.go (Go)

Code before:
		} else if !hasBackup {
			// since applying a backup does not save the applied backup to disk, we should
			// not calculate the original hash based on the backup data
			calcHash(b, &b.origHash)
		}
	}

	err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
	if err != nil {
		screen.TermMessage(err)
	}

	OpenBuffers = append(OpenBuffers, b)

	return b
}

// CloseOpenBuffers removes all open buffers
func CloseOpenBuffers() {
	for i, buf := range OpenBuffers {
		buf.Fini()
		OpenBuffers[i] = nil
	}
	OpenBuffers = OpenBuffers[:0]
}

// Close removes this buffer from the list of open buffers
func (b *Buffer) Close() {
	for i, buf := range OpenBuffers {
		if b == buf {
			b.Fini()
			copy(OpenBuffers[i:], OpenBuffers[i+1:])
			OpenBuffers[len(OpenBuffers)-1] = nil
			OpenBuffers = OpenBuffers[:len(OpenBuffers)-1]
		}
	}
}

// Fini should be called when a buffer is closed and performs
// some cleanup
func (b *Buffer) Fini() {
	if !b.Modified() {
		b.Serialize()
	}
	b.RemoveBackup()

	if b.Type == BTStdout {
		fmt.Fprint(util.Stdout, string(b.Bytes()))
	}

	atomic.StoreInt32(&(b.fini), int32(1))

Review comment (suggestion):
Return was redundant in `CloseOpenBuffers()` but it is not exactly redundant here in `Close()`: it is to break from the loop once we've found our buffer and thus don't need to traverse `OpenBuffers` anymore.

Code after:
		} else if !hasBackup {
			// since applying a backup does not save the applied backup to disk, we should
			// not calculate the original hash based on the backup data
			calcHash(b, &b.origHash)
		}
	}

	err = config.RunPluginFn("onBufferOpen", luar.New(ulua.L, b))
	if err != nil {
		screen.TermMessage(err)
	}

	OpenBuffers = append(OpenBuffers, b)

	return b
}

// CloseOpenBuffers removes all open buffers
func CloseOpenBuffers() {
	for i, buf := range OpenBuffers {
		buf.Fini()
		OpenBuffers[i] = nil
	}
	OpenBuffers = OpenBuffers[:0]
}

// Close removes this buffer from the list of open buffers
func (b *Buffer) Close() {
	for i, buf := range OpenBuffers {
		if b == buf {
			b.Fini()
			copy(OpenBuffers[i:], OpenBuffers[i+1:])
			OpenBuffers[len(OpenBuffers)-1] = nil
			OpenBuffers = OpenBuffers[:len(OpenBuffers)-1]
			return
		}
	}
}

// Fini should be called when a buffer is closed and performs
// some cleanup
func (b *Buffer) Fini() {
	if !b.Modified() {
		b.Serialize()
	}
	b.RemoveBackup()

	if b.Type == BTStdout {
		fmt.Fprint(util.Stdout, string(b.Bytes()))
	}
Repository: zyedidia/micro
File: pkg/highlight/parser.go (Go)

Code before:
package highlight

import (
	"bytes"
	"errors"
	"fmt"
	"regexp"

	"gopkg.in/yaml.v2"

	"github.com/zyedidia/micro/v2/internal/config"
	"github.com/zyedidia/micro/v2/internal/screen"
)

// A Group represents a syntax group
type Group uint8

// Groups contains all of the groups that are defined
// You can access them in the map via their string name
var Groups map[string]Group
var numGroups Group

// String returns the group name attached to the specific group
func (g Group) String() string {
	for k, v := range Groups {
		if v == g {
			return k
		}
	}
	return ""
}

// A Def is a full syntax definition for a language
// It has a filetype, information about how to detect the filetype based
// on filename or header (the first line of the file)
// Then it has the rules which define how to highlight the file
type Def struct {

Review comment (refactor):
I guess originally the `pkg/highlight` package was supposed to be "independent" of the rest of micro (e.g. to be potentially "reusable" in completely different projects, that are just required to implement the same `LineStates` interface as micro).

I doubt we still need to adhere to that assumption and cannot just assume the highlighter to be a part of micro and nothing more. However, it seems we can still easily avoid importing these micro's internal packages here. Is `pkg/highlight/parser.go` really the best place to add these helper functions to?

So now I'm thinking: isn't it better to add them to `internal/buffer/buffer.go`, just before `UpdateRules()`, just as private functions? I'm thinking about that because inside `UpdateRules()` you still aren't completely getting rid of all those `f.Data()`, `highlight.ParseFile()`, `screen.TermMessage()` and so on, i.e. they just become scattered between `UpdateRules()` itself and these new helper functions. So maybe it's better to keep them located close to `UpdateRules()` itself?

Code after:
package highlight

import (
	"bytes"
	"errors"
	"fmt"
	"regexp"

	"gopkg.in/yaml.v2"
)

// A Group represents a syntax group
type Group uint8

// Groups contains all of the groups that are defined
// You can access them in the map via their string name
var Groups map[string]Group
var numGroups Group

// String returns the group name attached to the specific group
func (g Group) String() string {
	for k, v := range Groups {
		if v == g {
			return k
		}
	}
	return ""
}

// A Def is a full syntax definition for a language
// It has a filetype, information about how to detect the filetype based
// on filename or header (the first line of the file)
// Then it has the rules which define how to highlight the file
type Def struct {
	*Header
	rules *rules
}