Adding upstream version 2.6.3.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
4d8cd0ce4c
commit
2b08a89310
39 changed files with 2140 additions and 0 deletions
23
.editorconfig
Normal file
23
.editorconfig
Normal file
|
@ -0,0 +1,23 @@
|
|||
; https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
eclint_indent_style = unset
|
||||
|
||||
[Dockerfile]
|
||||
indent_size = 4
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
* text=auto
|
13
.github/actions/core-test/Dockerfile
vendored
Normal file
13
.github/actions/core-test/Dockerfile
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
FROM golang:bullseye
|
||||
|
||||
SHELL [ "/bin/bash", "-x", "-e", "-c" ]
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteraactive
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -qy --no-install-recommends \
|
||||
cmake \
|
||||
git \
|
||||
make && \
|
||||
git config --system --add safe.directory '*'
|
||||
|
||||
CMD [ "make", "test-core", "test-skipped" ]
|
5
.github/actions/core-test/action.yml
vendored
Normal file
5
.github/actions/core-test/action.yml
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
name: Core test
|
||||
description: Run the editorconfig-core-test suite
|
||||
runs:
|
||||
using: docker
|
||||
image: Dockerfile
|
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,11 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: gomod
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: weekly
|
27
.github/workflows/golangci-lint.yml
vendored
Normal file
27
.github/workflows/golangci-lint.yml
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
name: golangci-lint
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v*
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
jobs:
|
||||
golangci:
|
||||
name: lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.23.x
|
||||
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@2226d7cb06a077cd73e56eedd38eecad18e5d837 #v6.5.0
|
||||
env:
|
||||
GOTOOLCHAIN: local
|
||||
with:
|
||||
version: v1.64
|
32
.github/workflows/main.yml
vendored
Normal file
32
.github/workflows/main.yml
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
on: [push]
|
||||
name: test and build
|
||||
jobs:
|
||||
editorconfig_lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: eclint-action
|
||||
uses: greut/eclint-action@v0
|
||||
go-test:
|
||||
runs-on: ubuntu-latest
|
||||
name: test
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
- name: go test
|
||||
run: go test -v ./...
|
||||
core-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: core test
|
||||
uses: ./.github/actions/core-test
|
20
.github/workflows/semgrep.yml
vendored
Normal file
20
.github/workflows/semgrep.yml
vendored
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Name of this GitHub Actions workflow.
|
||||
name: Semgrep
|
||||
|
||||
on:
|
||||
pull_request: {}
|
||||
|
||||
jobs:
|
||||
semgrep:
|
||||
name: Scan
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: returntocorp/semgrep
|
||||
# Skip any PR created by dependabot to avoid permission issues
|
||||
if: (github.actor != 'dependabot[bot]')
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- run: semgrep ci
|
||||
env:
|
||||
SEMGREP_RULES: p/default p/golang p/secrets
|
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
# ---> Go
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
# EditorConfig
|
||||
|
||||
/editorconfig
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "core-test"]
|
||||
path = core-test
|
||||
url = https://github.com/editorconfig/editorconfig-core-test.git
|
40
.golangci.yml
Normal file
40
.golangci.yml
Normal file
|
@ -0,0 +1,40 @@
|
|||
linters-settings:
|
||||
depguard:
|
||||
rules:
|
||||
main:
|
||||
files:
|
||||
- "!**/internal/**/*.go"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
- "github.com/editorconfig/editorconfig-core-go/v2"
|
||||
- "github.com/hashicorp/go-multierror"
|
||||
- "golang.org/x/mod/semver"
|
||||
- "gopkg.in/ini.v1"
|
||||
deny: []
|
||||
internal:
|
||||
files:
|
||||
- "**/internal/**/*.go"
|
||||
- "!$test"
|
||||
allow:
|
||||
- $gostd
|
||||
- "github.com/google/go-cmp"
|
||||
gci:
|
||||
sections:
|
||||
- standard
|
||||
- default
|
||||
- prefix(github.com/editorconfig/editorconfig-core-go)
|
||||
cyclop:
|
||||
max-complexity: 15
|
||||
package-average: 10
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- exhaustruct
|
||||
- mnd
|
||||
- tagliatelle
|
||||
- tenv
|
||||
- typecheck
|
||||
- varnamelen
|
||||
fast: false
|
76
.goreleaser.yml
Normal file
76
.goreleaser.yml
Normal file
|
@ -0,0 +1,76 @@
|
|||
version: 2
|
||||
|
||||
project_name: editorconfig-core-go
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy
|
||||
|
||||
builds:
|
||||
- id: editorconfig
|
||||
main: ./cmd/editorconfig/main.go
|
||||
binary: editorconfig
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
goos:
|
||||
- linux
|
||||
- darwin
|
||||
- windows
|
||||
|
||||
archives:
|
||||
- id: tarball
|
||||
builds:
|
||||
- editorconfig
|
||||
format_overrides:
|
||||
- goos: windows
|
||||
formats:
|
||||
- zip
|
||||
files:
|
||||
- none*
|
||||
|
||||
dockers:
|
||||
- image_templates:
|
||||
- ghcr.io/editorconfig/editorconfig-core-go/editorconfig:latest
|
||||
- ghcr.io/editorconfig/editorconfig-core-go/editorconfig:{{ .Tag }}
|
||||
- ghcr.io/editorconfig/editorconfig-core-go/editorconfig:v{{ .Major }}
|
||||
- ghcr.io/editorconfig/editorconfig-core-go/editorconfig:v{{ .Major }}.{{ .Minor }}
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
ids:
|
||||
- editorconfig
|
||||
build_flag_templates:
|
||||
- "--pull"
|
||||
- "--label=org.opencontainers.image.version={{ .Version }}"
|
||||
- "--label=org.opencontainers.image.title={{ .ProjectName }}"
|
||||
|
||||
nfpms:
|
||||
- vendor: EditorConfig
|
||||
homepage: https://github.com/editorconfig/editorconfig-core-go
|
||||
maintainer: Yoan Blanc <yoan@dosimple.ch>
|
||||
formats:
|
||||
- deb
|
||||
- rpm
|
||||
epoch: 1
|
||||
release: 1
|
||||
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
|
||||
signs:
|
||||
- artifacts: checksum
|
||||
|
||||
snapshot:
|
||||
version_template: "{{ .Tag }}-development"
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: editorconfig
|
||||
name: editorconfig-core-go
|
||||
draft: true
|
229
CHANGELOG.md
Normal file
229
CHANGELOG.md
Normal file
|
@ -0,0 +1,229 @@
|
|||
# Change log
|
||||
|
||||
## v2.6.3 - 2025-03-12
|
||||
|
||||
- Targets Go 1.22
|
||||
- Bump x/mod to 0.23
|
||||
- Bump google/go-cmp to 0.7.0
|
||||
|
||||
## v2.6.2 - 2024-04-02
|
||||
|
||||
- Fix paths on Windows
|
||||
- Bump golangci-lint to 1.56
|
||||
- Bump x/mod to 0.16.0
|
||||
|
||||
## v2.6.1 - 2024-01-29
|
||||
|
||||
- Fix utf-8-bom contains a dash
|
||||
- Bump google/go-cmp to 0.6.0
|
||||
- Bump x/mod to 0.14.0
|
||||
|
||||
## v2.6.0 - 2023-09-27
|
||||
|
||||
- Fix path matching on Windows. The spec says that:
|
||||
|
||||
> Backslashes (`\\`) are not allowed as path separators (even on Windows).
|
||||
- Replace go-multierror with Go 1.20 errors.Join
|
||||
|
||||
## v2.5.2 - 2023-04-19
|
||||
|
||||
- Bump golang.org/x/mod from 0.5.1 to 0.10.0
|
||||
|
||||
## v2.5.1 - 2022-10-02
|
||||
|
||||
- Fix typo in new method
|
||||
|
||||
## v2.5.0 - 2022-10-01
|
||||
|
||||
- Feature add a graceful parser that reports errors in the .editorconfig file as warnings
|
||||
- Bump Go version to 1.18 in the go.mod
|
||||
- Bump go.pkg.in/ini.v1 to 1.67.0
|
||||
- Bump google/go-cmp to 0.5.9
|
||||
|
||||
## v2.4.5 - 2022-06-18
|
||||
|
||||
- Bump Go version to 1.17 in the go.mod
|
||||
- Upgrade go.pkg.in/ini.v1 from 1.66.4 to 1.66.6
|
||||
- Upgrade github.com/google/go-cmp from 0.5.7 to 0.5.8
|
||||
|
||||
## v2.4.4 - 2022-03-17
|
||||
|
||||
- Upgrade gopkg.in/ini.v1 from 1.53 to 1.66.4;
|
||||
- Upgrade github.com/google/go-cmp from 0.5.6 to 0.5.7;
|
||||
- Upgrade golang.org/x/mod from 0.5.0 to 0.5.1.
|
||||
|
||||
## v2.4.3 - 2021-09-21
|
||||
|
||||
- Upgrade go-cmp v0.5.6
|
||||
([#110](https://github.com/editorconfig/editorconfig-core-go/pull/110));
|
||||
- Upgrade go-ini v1.63.2;
|
||||
- Upgrade x/mod v0.5.0
|
||||
([#111](https://github.com/editorconfig/editorconfig-core-go/pull/111));
|
||||
- Fix problems spotted by golangci-lint
|
||||
([#115](https://github.com/editorconfig/editorconfig-core-go/pull/115));
|
||||
|
||||
## v2.4.2 - 2021-03-21
|
||||
|
||||
- Upgrade google/go-cmp v0.5.5
|
||||
([#105](https://github.com/editorconfig/editorconfig-core-go/pull/105));
|
||||
- Upgrade x/mod v0.4.2
|
||||
([#106](https://github.com/editorconfig/editorconfig-core-go/pull/106)).
|
||||
|
||||
## v2.4.1 - 2021-02-25
|
||||
|
||||
- Fix for Go 1.16 os.IsNotExist wrapping
|
||||
([#102](https://github.com/editorconfig/editorconfig-core-go/pull/102)).
|
||||
|
||||
## v2.4.0 - 2021-02-22
|
||||
|
||||
- Fix new core-test
|
||||
([#100](https://github.com/editorconfig/editorconfig-core-go/pull/100));
|
||||
- Upgrade github CI versions
|
||||
([#99](https://github.com/editorconfig/editorconfig-core-go/pull/99));
|
||||
- Upgrade x/mod v0.4.1
|
||||
([#98](https://github.com/editorconfig/editorconfig-core-go/pull/98));
|
||||
- Fix goreleaser deprecations
|
||||
([#97](https://github.com/editorconfig/editorconfig-core-go/pull/97)).
|
||||
|
||||
## v2.3.10 - 2021-02-05
|
||||
|
||||
- Upgrade core-test
|
||||
([#93](https://github.com/editorconfig/editorconfig-core-go/pull/93));
|
||||
- Upgrade x/mod v0.4.0
|
||||
([#94](https://github.com/editorconfig/editorconfig-core-go/pull/94));
|
||||
- Upgrade golangci-lint to v1.34
|
||||
([#95](https://github.com/editorconfig/editorconfig-core-go/pull/95)).
|
||||
|
||||
## v2.3.9 - 2020-11-28
|
||||
|
||||
- Fix path separator on Windows
|
||||
([#69](https://github.com/editorconfig/editorconfig-core-go/pull/69));
|
||||
- Upgrade go-cmp v0.5.4
|
||||
([#91](https://github.com/editorconfig/editorconfig-core-go/pull/91)).
|
||||
|
||||
## v2.3.8 - 2020-10-17
|
||||
|
||||
- Feat more tests
|
||||
([#83](https://github.com/editorconfig/editorconfig-core-go/pull/83));
|
||||
- Upgrade go-ini v1.61.0
|
||||
([#84](https://github.com/editorconfig/editorconfig-core-go/pull/84));
|
||||
- Upgrade go-ini v1.62.0
|
||||
([#85](https://github.com/editorconfig/editorconfig-core-go/pull/85)).
|
||||
|
||||
## v2.3.7 - 2020-09-05
|
||||
|
||||
- Upgrade go-ini v1.60.2, and go-cmp v0.5.2
|
||||
([#81](https://github.com/editorconfig/editorconfig-core-go/pull/81)).
|
||||
|
||||
## v2.3.6 - 2020-08-25
|
||||
|
||||
- Use goerr113 linter
|
||||
([#77](https://github.com/editorconfig/editorconfig-core-go/pull/77));
|
||||
- Upgrade go-ini v1.60.0
|
||||
([#78](https://github.com/editorconfig/editorconfig-core-go/pull/78));
|
||||
- Upgrade go-ini v1.60.1
|
||||
([#79](https://github.com/editorconfig/editorconfig-core-go/pull/79)).
|
||||
|
||||
## v2.3.5 - 2020-08-20
|
||||
|
||||
- Upgrade go-cmp v0.5.1
|
||||
([#73](https://github.com/editorconfig/editorconfig-core-go/pull/73));
|
||||
- Replace custom GitHub Action with official GolangCI Lint
|
||||
([#74](https://github.com/editorconfig/editorconfig-core-go/pull/74));
|
||||
- Upgrade go-ini v1.58.0
|
||||
([#75](https://github.com/editorconfig/editorconfig-core-go/pull/75)).
|
||||
|
||||
## v2.3.4 - 2020-06-22
|
||||
|
||||
- Wrap errors using Go 1.13 syntax
|
||||
([#61](https://github.com/editorconfig/editorconfig-core-go/pull/61));
|
||||
- Upgrade base Docker image
|
||||
([#68](https://github.com/editorconfig/editorconfig-core-go/pull/68));
|
||||
- Upgrade go-ini v1.57.0, go-cmp v0.5.0
|
||||
([#70](https://github.com/editorconfig/editorconfig-core-go/pull/70)).
|
||||
|
||||
## v2.3.3 - 2020-05-19
|
||||
|
||||
- Using goreleaser
|
||||
([#22](https://github.com/editorconfig/editorconfig-core-go/pull/22));
|
||||
- Upgrade go-cmp, go-ini, x/mod
|
||||
([#60](https://github.com/editorconfig/editorconfig-core-go/pull/65));
|
||||
- Update CI actions
|
||||
([#63](https://github.com/editorconfig/editorconfig-core-go/pull/63));
|
||||
|
||||
## v2.3.2 - 2020-04-21
|
||||
|
||||
- Upgrade go-ini v1.55.0
|
||||
([#60](https://github.com/editorconfig/editorconfig-core-go/pull/60));
|
||||
- Build on latest Go
|
||||
([#54](https://github.com/editorconfig/editorconfig-core-go/pull/54));
|
||||
- Use GitHub action instead of Travis CI
|
||||
([#50](https://github.com/editorconfig/editorconfig-core-go/pull/50));
|
||||
|
||||
## v2.3.1 - 2020-03-16
|
||||
|
||||
- Use golang/x/mod/semver for semantic versioning checks
|
||||
([#55](https://github.com/editorconfig/editorconfig-core-go/pull/55));
|
||||
- Enable wsl (WhiteSpace linter)
|
||||
([#56](https://github.com/editorconfig/editorconfig-core-go/pull/56));
|
||||
- Replace testify dependency with Google's go-cmp
|
||||
([#57](https://github.com/editorconfig/editorconfig-core-go/pull/57));
|
||||
- Upgrade go-ini to v1.54.0
|
||||
([#58](https://github.com/editorconfig/editorconfig-core-go/pull/58)).
|
||||
|
||||
## v2.3.0 - 2020-02-14
|
||||
|
||||
- Implement a cached `Parser` to allow getting the definition of many files
|
||||
at once without re-reading the `.editorconfig` or parsing the _globbing_
|
||||
expression more than once.
|
||||
([#51](https://github.com/editorconfig/editorconfig-core-go/pull/51));
|
||||
- Run golangci-lint on travis
|
||||
([#26](https://github.com/editorconfig/editorconfig-core-go/pull/26)).
|
||||
|
||||
## v2.2.2 - 2020-01-19
|
||||
|
||||
- Bump core test to master
|
||||
([#42](https://github.com/editorconfig/editorconfig-core-go/pull/42));
|
||||
- Bugfix error mangled when reading a file which could create a panic
|
||||
([#47](https://github.com/editorconfig/editorconfig-core-go/pull/47));
|
||||
- Bugfix INI file generated would not show the correct value
|
||||
([#46](https://github.com/editorconfig/editorconfig-core-go/pull/46)).
|
||||
|
||||
## v2.2.1 - 2019-11-10
|
||||
|
||||
- Implement pre 0.9.0 behavior
|
||||
([#39](https://github.com/editorconfig/editorconfig-core-go/pull/39));
|
||||
- Fix values inheritance (regression)
|
||||
([#43](https://github.com/editorconfig/editorconfig-core-go/pull/43)).
|
||||
|
||||
## v2.2.0 - 2019-10-12
|
||||
|
||||
- Allow parsing from an `io.Reader`, effectively deprecating `ParseBytes`
|
||||
by [@mvdan](https://github.com/mvdan)
|
||||
([#32](https://github.com/editorconfig/editorconfig-core-go/pull/32));
|
||||
- Add support for the special `unset` value by [@greut](https://github.com/greut)
|
||||
([#19](https://github.com/editorconfig/editorconfig-core-go/pull/19));
|
||||
- Skip values, properties or section that are considered too long
|
||||
([#35](https://github.com/editorconfig/editorconfig-core-go/pull/35));
|
||||
- Clean up and documentation work by [@mstruebing](https://github.com/mstruebing/)
|
||||
([#23](https://github.com/editorconfig/editorconfig-core-go/pull/23),
|
||||
[#24](https://github.com/editorconfig/editorconfig-core-go/pull/24)).
|
||||
|
||||
## v2.1.1 - 2019-08-18
|
||||
|
||||
- Fix a small path bug
|
||||
([#17](https://github.com/editorconfig/editorconfig-core-go/issues/17),
|
||||
[#18](https://github.com/editorconfig/editorconfig-core-go/pull/18)).
|
||||
|
||||
## v2.1.0 - 2019-08-10
|
||||
|
||||
- This package is now *way* more compliant with the Editorconfig definition
|
||||
thanks to a refactor work made by [@greut](https://github.com/greut)
|
||||
([#15](https://github.com/editorconfig/editorconfig-core-go/pull/15)).
|
||||
|
||||
## v2.0.0 - 2019-07-14
|
||||
|
||||
- This project now uses [Go Modules](https://blog.golang.org/using-go-modules)
|
||||
([#14](https://github.com/editorconfig/editorconfig-core-go/pull/14));
|
||||
- The import path has been changed from `gopkg.in/editorconfig/editorconfig-core-go.v1`
|
||||
to `github.com/editorconfig/editorconfig-core-go/v2`.
|
5
CMakeLists.txt
Normal file
5
CMakeLists.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
project(editorconfig-core-go)
|
||||
enable_testing()
|
||||
set(EDITORCONFIG_CMD ${CMAKE_CURRENT_LIST_DIR}/editorconfig)
|
||||
add_subdirectory(core-test)
|
5
Dockerfile
Normal file
5
Dockerfile
Normal file
|
@ -0,0 +1,5 @@
|
|||
FROM alpine:3.21
|
||||
|
||||
COPY editorconfig /usr/local/bin/
|
||||
|
||||
ENTRYPOINT [ "editorconfig" ]
|
8
LICENSE
Normal file
8
LICENSE
Normal file
|
@ -0,0 +1,8 @@
|
|||
MIT License
|
||||
Copyright (c) 2016 The Editorconfig Team
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
35
Makefile
Normal file
35
Makefile
Normal file
|
@ -0,0 +1,35 @@
|
|||
PROJECT_ROOT_DIR := $(CURDIR)
|
||||
SRC := $(shell git ls-files *.go */*.go)
|
||||
|
||||
.PHONY: bin test test-go test-core test-skipped submodule
|
||||
|
||||
test: test-go test-core
|
||||
|
||||
submodule:
|
||||
git submodule update --init
|
||||
|
||||
editorconfig: $(SRC)
|
||||
go build \
|
||||
-ldflags "-X main.version=1.99.99" \
|
||||
github.com/editorconfig/editorconfig-core-go/v2/cmd/editorconfig
|
||||
|
||||
test-go:
|
||||
go test -v ./...
|
||||
|
||||
test-core: editorconfig
|
||||
cd core-test; \
|
||||
cmake ..
|
||||
cd core-test; \
|
||||
ctest \
|
||||
-E "^(octothorpe_in_value|(backslashed_)*semicolon_or_hash_in_property)$$" \
|
||||
--output-on-failure \
|
||||
.
|
||||
|
||||
test-skipped: editorconfig
|
||||
cd core-test; \
|
||||
cmake ..
|
||||
cd core-test; \
|
||||
ctest \
|
||||
-R "^(octothorpe_in_value)$$" \
|
||||
--show-only \
|
||||
.
|
151
README.md
Normal file
151
README.md
Normal file
|
@ -0,0 +1,151 @@
|
|||

|
||||
[](https://pkg.go.dev/github.com/editorconfig/editorconfig-core-go/v2)
|
||||
[](https://goreportcard.com/report/github.com/editorconfig/editorconfig-core-go)
|
||||
|
||||
# Editorconfig Core Go
|
||||
|
||||
A [Editorconfig][editorconfig] file parser and manipulator for Go.
|
||||
|
||||
## Missing features
|
||||
|
||||
- escaping comments in values, probably in [go-ini/ini](https://github.com/go-ini/ini)
|
||||
- [adjacent nested braces](https://github.com/editorconfig/editorconfig-core-test/pull/44)
|
||||
|
||||
## Installing
|
||||
|
||||
We recommend the use of Go 1.17+ modules for this package. Lower versions, such as 1.13, should be fine.
|
||||
|
||||
Import by the same path. The package name you will use to access it is
|
||||
`editorconfig`.
|
||||
|
||||
```go
|
||||
import "github.com/editorconfig/editorconfig-core-go/v2"
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Parse from a file
|
||||
|
||||
```go
|
||||
fp, err := os.Open("path/to/.editorconfig")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
editorConfig, err := editorconfig.Parse(fp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Graceful parsing from a file
|
||||
|
||||
```go
|
||||
fp, err := os.Open("path/to/.editorconfig")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fp.Close()
|
||||
|
||||
editorConfig, warning, err := editorconfig.ParseGraceful(fp)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Log the warning(s) encountered while reading the editorconfig file
|
||||
if warning != nil {
|
||||
log.Print(warning)
|
||||
}
|
||||
```
|
||||
|
||||
### Parse from slice of bytes
|
||||
|
||||
```go
|
||||
data := []byte("...")
|
||||
editorConfig, err := editorconfig.ParseBytes(data)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
### Get definition to a given filename
|
||||
|
||||
This method builds a definition to a given filename.
|
||||
This definition is a merge of the properties with selectors that matched the
|
||||
given filename.
|
||||
The lasts sections of the file have preference over the priors.
|
||||
|
||||
```go
|
||||
def := editorConfig.GetDefinitionForFilename("my/file.go")
|
||||
```
|
||||
|
||||
This definition have the following properties:
|
||||
|
||||
```go
|
||||
type Definition struct {
|
||||
Selector string
|
||||
|
||||
Charset string
|
||||
IndentStyle string
|
||||
IndentSize string
|
||||
TabWidth int
|
||||
EndOfLine string
|
||||
TrimTrailingWhitespace *bool
|
||||
InsertFinalNewline *bool
|
||||
Raw map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
#### Automatic search for `.editorconfig` files
|
||||
|
||||
If you want a definition of a file without having to manually
|
||||
parse the `.editorconfig` files, you can then use the static version
|
||||
of `GetDefinitionForFilename`:
|
||||
|
||||
```go
|
||||
def, err := editorconfig.GetDefinitionForFilename("foo/bar/baz/my-file.go")
|
||||
```
|
||||
|
||||
In the example above, the package will automatically search for
|
||||
`.editorconfig` files on:
|
||||
|
||||
- `foo/bar/baz/.editorconfig`
|
||||
- `foo/baz/.editorconfig`
|
||||
- `foo/.editorconfig`
|
||||
|
||||
Until it reaches a file with `root = true` or the root of the filesystem.
|
||||
|
||||
### Generating a .editorconfig file
|
||||
|
||||
You can easily convert a Editorconfig struct to a compatible INI file:
|
||||
|
||||
```go
|
||||
// serialize to slice of bytes
|
||||
data, err := editorConfig.Serialize()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// save directly to file
|
||||
err := editorConfig.Save("path/to/.editorconfig")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
To run the tests:
|
||||
|
||||
```bash
|
||||
go test -v ./...
|
||||
```
|
||||
|
||||
To run the [integration tests](https://github.com/editorconfig/editorconfig-core-test):
|
||||
|
||||
```bash
|
||||
make test-core
|
||||
```
|
||||
|
||||
[editorconfig]: https://editorconfig.org/
|
89
cached_parser.go
Normal file
89
cached_parser.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
package editorconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// CachedParser implements the Parser interface but caches the definition and
|
||||
// the regular expressions.
|
||||
type CachedParser struct {
|
||||
editorconfigs map[string]*Editorconfig
|
||||
regexps map[string]*regexp.Regexp
|
||||
}
|
||||
|
||||
// NewCachedParser initializes the CachedParser.
|
||||
func NewCachedParser() *CachedParser {
|
||||
return &CachedParser{
|
||||
editorconfigs: make(map[string]*Editorconfig),
|
||||
regexps: make(map[string]*regexp.Regexp),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseIni parses the given filename to a Definition and caches the result.
|
||||
func (parser *CachedParser) ParseIni(filename string) (*Editorconfig, error) {
|
||||
ec, warning, err := parser.ParseIniGraceful(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ec, warning
|
||||
}
|
||||
|
||||
// ParseIniGraceful parses the given filename to a Definition and caches the result.
|
||||
func (parser *CachedParser) ParseIniGraceful(filename string) (*Editorconfig, error, error) {
|
||||
var warning error
|
||||
|
||||
ec, ok := parser.editorconfigs[filename]
|
||||
if !ok {
|
||||
fp, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error opening %q: %w", filename, err)
|
||||
}
|
||||
|
||||
defer fp.Close()
|
||||
|
||||
iniFile, err := ini.Load(fp)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error loading ini file %q: %w", filename, err)
|
||||
}
|
||||
|
||||
var warn error
|
||||
|
||||
ec, warn, err = newEditorconfig(iniFile)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating config: %w", err)
|
||||
}
|
||||
|
||||
if warn != nil {
|
||||
warning = errors.Join(warning, warn)
|
||||
}
|
||||
|
||||
parser.editorconfigs[filename] = ec
|
||||
}
|
||||
|
||||
return ec, warning, nil
|
||||
}
|
||||
|
||||
// FnmatchCase calls the module's FnmatchCase and caches the parsed selector.
|
||||
func (parser *CachedParser) FnmatchCase(selector string, filename string) (bool, error) {
|
||||
r, ok := parser.regexps[selector]
|
||||
if !ok {
|
||||
p := translate(selector)
|
||||
|
||||
var err error
|
||||
|
||||
r, err = regexp.Compile(fmt.Sprintf("^%s$", p))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error compiling selector %q: %w", selector, err)
|
||||
}
|
||||
|
||||
parser.regexps[selector] = r
|
||||
}
|
||||
|
||||
return r.MatchString(filename), nil
|
||||
}
|
74
cmd/editorconfig/main.go
Normal file
74
cmd/editorconfig/main.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/editorconfig/editorconfig-core-go/v2"
|
||||
)
|
||||
|
||||
// version indicates the current version number.
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
var (
|
||||
configName string
|
||||
configVersion string
|
||||
showVersionFlag bool
|
||||
)
|
||||
|
||||
flag.StringVar(&configName, "f", editorconfig.ConfigNameDefault, "Specify conf filename other than '.editorconfig'")
|
||||
flag.StringVar(&configVersion, "b", "", "Specify version (used by devs to test compatibility)")
|
||||
flag.BoolVar(&showVersionFlag, "v", false, "Display version information")
|
||||
flag.BoolVar(&showVersionFlag, "version", false, "Display version information")
|
||||
flag.Parse()
|
||||
|
||||
if showVersionFlag {
|
||||
fmt.Printf("EditorConfig Core Go, Version %s\n", version) //nolint:forbidigo
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
rest := flag.Args()
|
||||
|
||||
if len(rest) < 1 {
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config := &editorconfig.Config{
|
||||
Name: configName,
|
||||
Version: configVersion,
|
||||
Graceful: false,
|
||||
}
|
||||
|
||||
if len(rest) > 1 {
|
||||
config.Parser = editorconfig.NewCachedParser()
|
||||
}
|
||||
|
||||
for _, file := range rest {
|
||||
def, err := config.Load(file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
iniFile := ini.Empty()
|
||||
ini.PrettyFormat = false
|
||||
|
||||
if len(rest) < 2 {
|
||||
def.Selector = ini.DefaultSection
|
||||
} else {
|
||||
def.Selector = file
|
||||
}
|
||||
|
||||
def.InsertToIniFile(iniFile)
|
||||
|
||||
_, err = iniFile.WriteTo(os.Stdout)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
111
config.go
Normal file
111
config.go
Normal file
|
@ -0,0 +1,111 @@
|
|||
package editorconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
// ErrInvalidVersion represents a standard error with the semantic version.
|
||||
var ErrInvalidVersion = errors.New("invalid semantic version")
|
||||
|
||||
// Config holds the configuration.
|
||||
type Config struct {
|
||||
Path string
|
||||
Name string
|
||||
Version string
|
||||
Parser Parser
|
||||
Graceful bool
|
||||
}
|
||||
|
||||
// Load loads definition of a given file.
|
||||
func (config *Config) Load(filename string) (*Definition, error) {
|
||||
definition, warning, err := config.LoadGraceful(filename)
|
||||
if warning != nil {
|
||||
err = errors.Join(err, warning)
|
||||
}
|
||||
|
||||
return definition, err
|
||||
}
|
||||
|
||||
// Load loads definition of a given file with warnings and error.
|
||||
func (config *Config) LoadGraceful(filename string) (*Definition, error, error) { //nolint:funlen
|
||||
// idiomatic go allows empty struct
|
||||
if config.Parser == nil {
|
||||
config.Parser = new(SimpleParser)
|
||||
}
|
||||
|
||||
absFilename, err := filepath.Abs(filename)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot get absolute path for %q: %w", filename, err)
|
||||
}
|
||||
|
||||
ecFile := config.Name
|
||||
if ecFile == "" {
|
||||
ecFile = ConfigNameDefault
|
||||
}
|
||||
|
||||
definition := &Definition{}
|
||||
definition.Raw = make(map[string]string)
|
||||
|
||||
if config.Version != "" {
|
||||
version := config.Version
|
||||
if !strings.HasPrefix(version, "v") {
|
||||
version = "v" + version
|
||||
}
|
||||
|
||||
if ok := semver.IsValid(version); !ok {
|
||||
return nil, nil, fmt.Errorf("version %s error: %w", config.Version, ErrInvalidVersion)
|
||||
}
|
||||
|
||||
definition.version = version
|
||||
}
|
||||
|
||||
var warning error
|
||||
|
||||
dir := absFilename
|
||||
for dir != filepath.Dir(dir) {
|
||||
dir = filepath.Dir(dir)
|
||||
|
||||
ec, warn, err := config.Parser.ParseIniGraceful(filepath.Join(dir, ecFile))
|
||||
if warn != nil {
|
||||
warning = errors.Join(warning, warn)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
|
||||
return nil, nil, fmt.Errorf("cannot parse the ini file %q: %w", ecFile, err)
|
||||
}
|
||||
|
||||
// give it the current config.
|
||||
ec.config = config
|
||||
|
||||
relativeFilename := absFilename
|
||||
if len(dir) < len(relativeFilename) {
|
||||
relativeFilename = relativeFilename[len(dir):]
|
||||
}
|
||||
|
||||
// turn any Windows-y filename into the standard forward slash ones.
|
||||
relativeFilename = filepath.ToSlash(relativeFilename)
|
||||
|
||||
def, err := ec.GetDefinitionForFilename(relativeFilename)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot get definition for %q: %w", relativeFilename, err)
|
||||
}
|
||||
|
||||
definition.merge(def)
|
||||
|
||||
if ec.Root {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return definition, warning, nil
|
||||
}
|
203
definition.go
Normal file
203
definition.go
Normal file
|
@ -0,0 +1,203 @@
|
|||
package editorconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// Definition represents a definition inside the .editorconfig file.
|
||||
// E.g. a section of the file.
|
||||
// The definition is composed of the selector ("*", "*.go", "*.{js.css}", etc),
|
||||
// plus the properties of the selected files.
|
||||
type Definition struct {
|
||||
Selector string `ini:"-" json:"-"`
|
||||
|
||||
Charset string `ini:"charset" json:"charset,omitempty"`
|
||||
IndentStyle string `ini:"indent_style" json:"indent_style,omitempty"`
|
||||
IndentSize string `ini:"indent_size" json:"indent_size,omitempty"`
|
||||
TabWidth int `ini:"-" json:"-"`
|
||||
EndOfLine string `ini:"end_of_line" json:"end_of_line,omitempty"`
|
||||
TrimTrailingWhitespace *bool `ini:"-" json:"-"`
|
||||
InsertFinalNewline *bool `ini:"-" json:"-"`
|
||||
Raw map[string]string `ini:"-" json:"-"`
|
||||
version string
|
||||
}
|
||||
|
||||
// NewDefinition builds a definition from a given config.
|
||||
func NewDefinition(config Config) (*Definition, error) {
|
||||
return config.Load(config.Path)
|
||||
}
|
||||
|
||||
// normalize fixes some values to their lowercase value.
|
||||
func (d *Definition) normalize() error {
|
||||
var result error
|
||||
|
||||
d.Charset = strings.ToLower(d.Charset)
|
||||
d.EndOfLine = strings.ToLower(d.Raw["end_of_line"])
|
||||
d.IndentStyle = strings.ToLower(d.Raw["indent_style"])
|
||||
|
||||
trimTrailingWhitespace, ok := d.Raw["trim_trailing_whitespace"]
|
||||
if ok && trimTrailingWhitespace != UnsetValue {
|
||||
trim, err := strconv.ParseBool(trimTrailingWhitespace)
|
||||
if err != nil {
|
||||
result = errors.Join(
|
||||
result,
|
||||
fmt.Errorf("trim_trailing_whitespace=%s is not an acceptable value. %w", trimTrailingWhitespace, err),
|
||||
)
|
||||
} else {
|
||||
d.TrimTrailingWhitespace = &trim
|
||||
}
|
||||
}
|
||||
|
||||
insertFinalNewline, ok := d.Raw["insert_final_newline"]
|
||||
if ok && insertFinalNewline != UnsetValue {
|
||||
insert, err := strconv.ParseBool(insertFinalNewline)
|
||||
if err != nil {
|
||||
result = errors.Join(
|
||||
result,
|
||||
fmt.Errorf("insert_final_newline=%s is not an acceptable value. %w", insertFinalNewline, err),
|
||||
)
|
||||
} else {
|
||||
d.InsertFinalNewline = &insert
|
||||
}
|
||||
}
|
||||
|
||||
// tab_width from Raw
|
||||
tabWidth, ok := d.Raw["tab_width"]
|
||||
if ok && tabWidth != UnsetValue {
|
||||
num, err := strconv.Atoi(tabWidth)
|
||||
if err != nil {
|
||||
result = errors.Join(result, fmt.Errorf("tab_width=%s is not an acceptable value. %w", tabWidth, err))
|
||||
} else {
|
||||
d.TabWidth = num
|
||||
}
|
||||
}
|
||||
|
||||
// tab_width defaults to indent_size:
|
||||
// https://github.com/editorconfig/editorconfig/wiki/EditorConfig-Properties#tab_width
|
||||
num, err := strconv.Atoi(d.IndentSize)
|
||||
if err == nil && d.TabWidth <= 0 {
|
||||
d.TabWidth = num
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// merge the parent definition into the child definition.
|
||||
func (d *Definition) merge(md *Definition) {
|
||||
if len(d.Charset) == 0 {
|
||||
d.Charset = md.Charset
|
||||
}
|
||||
|
||||
if len(d.IndentStyle) == 0 {
|
||||
d.IndentStyle = md.IndentStyle
|
||||
}
|
||||
|
||||
if len(d.IndentSize) == 0 {
|
||||
d.IndentSize = md.IndentSize
|
||||
}
|
||||
|
||||
if d.TabWidth <= 0 {
|
||||
d.TabWidth = md.TabWidth
|
||||
}
|
||||
|
||||
if len(d.EndOfLine) == 0 {
|
||||
d.EndOfLine = md.EndOfLine
|
||||
}
|
||||
|
||||
if trimTrailingWhitespace, ok := d.Raw["trim_trailing_whitespace"]; !ok || trimTrailingWhitespace != UnsetValue {
|
||||
if d.TrimTrailingWhitespace == nil {
|
||||
d.TrimTrailingWhitespace = md.TrimTrailingWhitespace
|
||||
}
|
||||
}
|
||||
|
||||
if insertFinalNewline, ok := d.Raw["insert_final_newline"]; !ok || insertFinalNewline != UnsetValue {
|
||||
if d.InsertFinalNewline == nil {
|
||||
d.InsertFinalNewline = md.InsertFinalNewline
|
||||
}
|
||||
}
|
||||
|
||||
for k, v := range md.Raw {
|
||||
if _, ok := d.Raw[k]; !ok {
|
||||
d.Raw[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// InsertToIniFile writes the definition into a ini file.
|
||||
func (d *Definition) InsertToIniFile(iniFile *ini.File) { //nolint:funlen,gocognit,cyclop
|
||||
iniSec := iniFile.Section(d.Selector)
|
||||
|
||||
for k, v := range d.Raw {
|
||||
switch k {
|
||||
case "insert_final_newline":
|
||||
if d.InsertFinalNewline != nil {
|
||||
v = strconv.FormatBool(*d.InsertFinalNewline)
|
||||
} else {
|
||||
insertFinalNewline, ok := d.Raw["insert_final_newline"]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
v = strings.ToLower(insertFinalNewline)
|
||||
}
|
||||
case "trim_trailing_whitespace":
|
||||
if d.TrimTrailingWhitespace != nil {
|
||||
v = strconv.FormatBool(*d.TrimTrailingWhitespace)
|
||||
} else {
|
||||
trimTrailingWhitespace, ok := d.Raw["trim_trailing_whitespace"]
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
v = strings.ToLower(trimTrailingWhitespace)
|
||||
}
|
||||
case "charset":
|
||||
v = d.Charset
|
||||
case "end_of_line":
|
||||
v = d.EndOfLine
|
||||
case "indent_style":
|
||||
v = d.IndentStyle
|
||||
case "tab_width":
|
||||
tabWidth, ok := d.Raw["tab_width"]
|
||||
if ok && tabWidth == UnsetValue {
|
||||
v = tabWidth
|
||||
} else {
|
||||
v = strconv.Itoa(d.TabWidth)
|
||||
}
|
||||
case "indent_size":
|
||||
v = d.IndentSize
|
||||
}
|
||||
|
||||
iniSec.NewKey(k, v) //nolint:errcheck
|
||||
}
|
||||
|
||||
if _, ok := d.Raw["indent_size"]; !ok {
|
||||
tabWidth, ok := d.Raw["tab_width"]
|
||||
|
||||
switch {
|
||||
case ok && tabWidth == UnsetValue:
|
||||
// do nothing
|
||||
case d.TabWidth > 0:
|
||||
iniSec.NewKey("indent_size", strconv.Itoa(d.TabWidth)) //nolint:errcheck
|
||||
case d.IndentStyle == IndentStyleTab && (d.version == "" || semver.Compare(d.version, "v0.9.0") >= 0):
|
||||
iniSec.NewKey("indent_size", IndentStyleTab) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := d.Raw["tab_width"]; !ok {
|
||||
if d.IndentSize == UnsetValue {
|
||||
iniSec.NewKey("tab_width", d.IndentSize) //nolint:errcheck
|
||||
} else {
|
||||
_, err := strconv.Atoi(d.IndentSize)
|
||||
if err == nil {
|
||||
iniSec.NewKey("tab_width", d.Raw["indent_size"]) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
310
editorconfig.go
Normal file
310
editorconfig.go
Normal file
|
@ -0,0 +1,310 @@
|
|||
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)
|
||||
}
|
179
editorconfig_test.go
Normal file
179
editorconfig_test.go
Normal file
|
@ -0,0 +1,179 @@
|
|||
package editorconfig //nolint:testpackage
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/iotest"
|
||||
|
||||
"github.com/editorconfig/editorconfig-core-go/v2/internal/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
testFile = "testdata/.editorconfig"
|
||||
)
|
||||
|
||||
func testParse(t *testing.T, ec *Editorconfig) {
|
||||
t.Helper()
|
||||
|
||||
assert.Equal(t, true, ec.Root)
|
||||
assert.Equal(t, 3, len(ec.Definitions))
|
||||
|
||||
def := ec.Definitions[0]
|
||||
assert.Equal(t, "*", def.Selector)
|
||||
assert.Equal(t, EndOfLineLf, def.EndOfLine)
|
||||
assert.Equal(t, true, *def.InsertFinalNewline)
|
||||
assert.Equal(t, CharsetUTF8, def.Charset)
|
||||
assert.Equal(t, true, *def.TrimTrailingWhitespace)
|
||||
assert.Equal(t, "8", def.IndentSize)
|
||||
|
||||
def = ec.Definitions[1]
|
||||
assert.Equal(t, "*.go", def.Selector)
|
||||
assert.Equal(t, IndentStyleTab, def.IndentStyle)
|
||||
assert.Equal(t, "4", def.IndentSize)
|
||||
assert.Equal(t, 4, def.TabWidth)
|
||||
|
||||
def = ec.Definitions[2]
|
||||
assert.Equal(t, "*.{js,css,less,htm,html}", def.Selector)
|
||||
assert.Equal(t, IndentStyleSpaces, def.IndentStyle)
|
||||
assert.Equal(t, "2", def.IndentSize)
|
||||
assert.Equal(t, 2, def.TabWidth)
|
||||
}
|
||||
|
||||
func TestParseFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ec, err := ParseFile(testFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
testParse(t, ec)
|
||||
}
|
||||
|
||||
func TestParseBytes(t *testing.T) { //nolint:paralleltest
|
||||
data, err := os.ReadFile(testFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
ec, err := ParseBytes(data)
|
||||
assert.Nil(t, err)
|
||||
|
||||
testParse(t, ec)
|
||||
}
|
||||
|
||||
func TestParseReader(t *testing.T) { //nolint:paralleltest
|
||||
f, err := os.Open(testFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defer f.Close()
|
||||
|
||||
ec, err := Parse(f)
|
||||
assert.Nil(t, err)
|
||||
|
||||
testParse(t, ec)
|
||||
}
|
||||
|
||||
func TestParseReaderTimeoutError(t *testing.T) { //nolint:paralleltest
|
||||
f, err := os.Open(testFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defer f.Close()
|
||||
|
||||
_, err = Parse(iotest.TimeoutReader(f))
|
||||
assert.Equal(t, true, errors.Is(err, iotest.ErrTimeout))
|
||||
}
|
||||
|
||||
func TestGetDefinition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ec, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse file: %v", err)
|
||||
}
|
||||
|
||||
def, err := ec.GetDefinitionForFilename("main.go")
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse file: %v", err)
|
||||
}
|
||||
|
||||
assert.Equal(t, IndentStyleTab, def.IndentStyle)
|
||||
assert.Equal(t, "4", def.IndentSize)
|
||||
assert.Equal(t, 4, def.TabWidth)
|
||||
assert.Equal(t, true, *def.TrimTrailingWhitespace)
|
||||
assert.Equal(t, CharsetUTF8, def.Charset)
|
||||
assert.Equal(t, true, *def.InsertFinalNewline)
|
||||
assert.Equal(t, EndOfLineLf, def.EndOfLine)
|
||||
}
|
||||
|
||||
func TestWrite(t *testing.T) { //nolint:paralleltest
|
||||
ec, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse file: %v", err)
|
||||
}
|
||||
|
||||
tempFile := filepath.Join(os.TempDir(), ".editorconfig")
|
||||
|
||||
f, err := os.OpenFile(tempFile, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
|
||||
assert.Nil(t, err)
|
||||
|
||||
defer func() {
|
||||
f.Close()
|
||||
os.Remove(tempFile)
|
||||
}()
|
||||
|
||||
err = ec.Write(f)
|
||||
assert.Nil(t, err)
|
||||
|
||||
savedEc, err := ParseFile(tempFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
testParse(t, savedEc)
|
||||
}
|
||||
|
||||
func TestSave(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ec, err := ParseFile(testFile)
|
||||
if err != nil {
|
||||
t.Errorf("Couldn't parse file: %v", err)
|
||||
}
|
||||
|
||||
tempFile := filepath.Join(os.TempDir(), ".editorconfig")
|
||||
defer os.Remove(tempFile)
|
||||
|
||||
err = ec.Save(tempFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
savedEc, err := ParseFile(tempFile)
|
||||
assert.Nil(t, err)
|
||||
|
||||
testParse(t, savedEc)
|
||||
}
|
||||
|
||||
func TestPublicTestDefinitionForFilename(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
def, err := GetDefinitionForFilename("testdata/root/src/dummy.go")
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "4", def.IndentSize)
|
||||
assert.Equal(t, IndentStyleTab, def.IndentStyle)
|
||||
assert.Equal(t, true, *def.InsertFinalNewline)
|
||||
assert.Equal(t, (*bool)(nil), def.TrimTrailingWhitespace)
|
||||
}
|
||||
|
||||
func TestPublicTestDefinitionForFilenameWithConfigname(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
def, warning, err := GetDefinitionForFilenameWithConfignameGraceful("testdata/root/src/dummy.go", "a.ini")
|
||||
|
||||
// Checking that we've got three warnings by splitting the lines
|
||||
message := warning.Error()
|
||||
merr := strings.Split(message, "\n")
|
||||
assert.Equal(t, 3, len(merr))
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "5", def.IndentSize)
|
||||
assert.Equal(t, IndentStyleSpaces, def.IndentStyle)
|
||||
assert.Equal(t, false, *def.InsertFinalNewline)
|
||||
assert.Equal(t, false, *def.TrimTrailingWhitespace)
|
||||
}
|
213
fnmatch.go
Normal file
213
fnmatch.go
Normal file
|
@ -0,0 +1,213 @@
|
|||
package editorconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// findLeftBrackets matches the opening left bracket {.
|
||||
findLeftBrackets = regexp.MustCompile(`(^|[^\\])\{`)
|
||||
// findDoubleLeftBrackets matches the duplicated opening left bracket {{.
|
||||
findDoubleLeftBrackets = regexp.MustCompile(`(^|[^\\])\{\{`)
|
||||
// findLeftBrackets matches the closing right bracket {.
|
||||
findRightBrackets = regexp.MustCompile(`(^|[^\\])\}`)
|
||||
// findDoubleRightBrackets matches the duplicated opening left bracket {{.
|
||||
findDoubleRightBrackets = regexp.MustCompile(`(^|[^\\])\}\}`)
|
||||
// findNumericRange matches a range of number, e.g. -2..5.
|
||||
findNumericRange = regexp.MustCompile(`^([+-]?\d+)\.\.([+-]?\d+)$`)
|
||||
)
|
||||
|
||||
// FnmatchCase tests whether the name matches the given pattern case included.
|
||||
func FnmatchCase(pattern, name string) (bool, error) {
|
||||
p := translate(pattern)
|
||||
|
||||
r, err := regexp.Compile(fmt.Sprintf("^%s$", p))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error compiling %q: %w", pattern, err)
|
||||
}
|
||||
|
||||
return r.MatchString(name), nil
|
||||
}
|
||||
|
||||
func translate(pattern string) string { //nolint:funlen,gocognit,gocyclo,cyclop,maintidx
|
||||
index := 0
|
||||
pat := []rune(pattern)
|
||||
length := len(pat)
|
||||
|
||||
result := strings.Builder{}
|
||||
|
||||
braceLevel := 0
|
||||
isEscaped := false
|
||||
inBrackets := false
|
||||
|
||||
// Double left and right is a hack to pass the core-test suite.
|
||||
left := len(findLeftBrackets.FindAllString(pattern, -1))
|
||||
doubleLeft := len(findDoubleLeftBrackets.FindAllString(pattern, -1))
|
||||
right := len(findRightBrackets.FindAllString(pattern, -1))
|
||||
doubleRight := len(findDoubleRightBrackets.FindAllString(pattern, -1))
|
||||
matchesBraces := left+doubleLeft == right+doubleRight
|
||||
pathSeparator := "/"
|
||||
|
||||
for index < length {
|
||||
r := pat[index]
|
||||
index++
|
||||
|
||||
switch r {
|
||||
case '*':
|
||||
p := index
|
||||
if p < length && pat[p] == '*' {
|
||||
result.WriteString(".*")
|
||||
|
||||
index++
|
||||
} else {
|
||||
result.WriteString(fmt.Sprintf("[^%s]*", pathSeparator))
|
||||
}
|
||||
case '/':
|
||||
p := index
|
||||
if p+2 < length && pat[p] == '*' && pat[p+1] == '*' && pat[p+2] == '/' {
|
||||
result.WriteString(fmt.Sprintf("(?:%s|%s.*%s)", pathSeparator, pathSeparator, pathSeparator))
|
||||
|
||||
index += 3
|
||||
} else {
|
||||
result.WriteRune(r)
|
||||
}
|
||||
case '?':
|
||||
result.WriteString(fmt.Sprintf("[^%s]", pathSeparator))
|
||||
case '[':
|
||||
if inBrackets { //nolint:nestif
|
||||
result.WriteString("\\[")
|
||||
} else {
|
||||
hasSlash := false
|
||||
res := strings.Builder{}
|
||||
|
||||
p := index
|
||||
for p < length {
|
||||
if pat[p] == ']' && pat[p-1] != '\\' {
|
||||
break
|
||||
}
|
||||
|
||||
res.WriteRune(pat[p])
|
||||
|
||||
if pat[p] == '/' && pat[p-1] != '\\' {
|
||||
hasSlash = true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
p++
|
||||
}
|
||||
|
||||
if hasSlash {
|
||||
result.WriteString("\\[" + res.String())
|
||||
|
||||
index = p + 1
|
||||
} else {
|
||||
if index < length && pat[index] == '!' || pat[index] == '^' {
|
||||
result.WriteString("[^")
|
||||
|
||||
index++
|
||||
} else {
|
||||
result.WriteRune('[')
|
||||
}
|
||||
|
||||
inBrackets = true
|
||||
}
|
||||
}
|
||||
case ']':
|
||||
if inBrackets && pat[index-2] == '\\' {
|
||||
result.WriteString("\\]")
|
||||
} else {
|
||||
result.WriteRune(r)
|
||||
|
||||
inBrackets = false
|
||||
}
|
||||
case '{':
|
||||
hasComma := false
|
||||
p := index
|
||||
res := strings.Builder{}
|
||||
|
||||
for p < length {
|
||||
if pat[p] == '}' && pat[p-1] != '\\' {
|
||||
break
|
||||
}
|
||||
|
||||
res.WriteRune(pat[p])
|
||||
|
||||
if pat[p] == ',' && pat[p-1] != '\\' {
|
||||
hasComma = true
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
p++
|
||||
}
|
||||
|
||||
switch {
|
||||
case !hasComma && p < length:
|
||||
inner := res.String()
|
||||
|
||||
sub := findNumericRange.FindStringSubmatch(inner)
|
||||
if len(sub) == 3 {
|
||||
from, _ := strconv.Atoi(sub[1])
|
||||
to, _ := strconv.Atoi(sub[2])
|
||||
|
||||
result.WriteString("(?:")
|
||||
|
||||
// XXX does not scale well
|
||||
for i := from; i < to; i++ {
|
||||
result.WriteString(strconv.Itoa(i))
|
||||
result.WriteRune('|')
|
||||
}
|
||||
|
||||
result.WriteString(strconv.Itoa(to))
|
||||
result.WriteRune(')')
|
||||
} else {
|
||||
r := translate(inner)
|
||||
|
||||
result.WriteString(fmt.Sprintf("\\{%s\\}", r))
|
||||
}
|
||||
|
||||
index = p + 1
|
||||
case matchesBraces:
|
||||
result.WriteString("(?:")
|
||||
|
||||
braceLevel++
|
||||
default:
|
||||
result.WriteString("\\{")
|
||||
}
|
||||
case '}':
|
||||
if braceLevel > 0 {
|
||||
if isEscaped {
|
||||
result.WriteRune('}')
|
||||
|
||||
isEscaped = false
|
||||
} else {
|
||||
result.WriteRune(')')
|
||||
|
||||
braceLevel--
|
||||
}
|
||||
} else {
|
||||
result.WriteString("\\}")
|
||||
}
|
||||
case ',':
|
||||
if braceLevel == 0 || isEscaped {
|
||||
result.WriteRune(r)
|
||||
} else {
|
||||
result.WriteRune('|')
|
||||
}
|
||||
default:
|
||||
if r != '\\' || isEscaped {
|
||||
result.WriteString(regexp.QuoteMeta(string(r)))
|
||||
|
||||
isEscaped = false
|
||||
} else {
|
||||
isEscaped = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
40
fnmatch_test.go
Normal file
40
fnmatch_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package editorconfig //nolint:testpackage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTranslate(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
pattern string
|
||||
expected string
|
||||
}{
|
||||
{"a*e.c", `a[^/]*e\.c`},
|
||||
{"a**z.c", `a.*z\.c`},
|
||||
{"d/**/z.c", `d(?:/|/.*/)z\.c`},
|
||||
{"som?.c", `som[^/]\.c`},
|
||||
{"[\\]ab].g", `[\]ab]\.g`},
|
||||
{"[ab]].g", `[ab]]\.g`},
|
||||
{"ab[/c", `ab\[/c`},
|
||||
{"*.{py,js,html}", `[^/]*\.(?:py|js|html)`},
|
||||
{"{single}.b", `\{single\}\.b`},
|
||||
{"{{,b,c{d}.i", `\{\{,b,c\{d\}\.i`},
|
||||
{"{a\\,b,cd}", `(?:a,b|cd)`},
|
||||
{"{e,\\},f}", `(?:e|}|f)`},
|
||||
{"{a,{b,c}}", `(?:a|(?:b|c))`},
|
||||
{"{{a,b},c}", `(?:(?:a|b)|c)`},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.pattern, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result := translate(test.pattern)
|
||||
if result != test.expected {
|
||||
t.Errorf("%s != %s", test.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
2
go.doc
Normal file
2
go.doc
Normal file
|
@ -0,0 +1,2 @@
|
|||
// Package editorconfig can be used to parse and generate editorconfig files.
|
||||
// For more information about editorconfig, see https://editorconfig.org/
|
13
go.mod
Normal file
13
go.mod
Normal file
|
@ -0,0 +1,13 @@
|
|||
module github.com/editorconfig/editorconfig-core-go/v2
|
||||
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.0
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.7.0
|
||||
golang.org/x/mod v0.23.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
require github.com/stretchr/testify v1.7.0 // indirect
|
16
go.sum
Normal file
16
go.sum
Normal file
|
@ -0,0 +1,16 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM=
|
||||
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
22
internal/assert/assert.go
Normal file
22
internal/assert/assert.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package assert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
func Equal(t *testing.T, x, y interface{}) {
|
||||
t.Helper()
|
||||
|
||||
r := DiffReporter{}
|
||||
if !cmp.Equal(x, y, cmp.Reporter(&r)) {
|
||||
t.Error(r.String())
|
||||
}
|
||||
}
|
||||
|
||||
func Nil(t *testing.T, x interface{}) {
|
||||
t.Helper()
|
||||
|
||||
Equal(t, x, nil)
|
||||
}
|
36
internal/assert/diff_reporter.go
Normal file
36
internal/assert/diff_reporter.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
// this code is from the examples
|
||||
// https://pkg.go.dev/github.com/google/go-cmp/cmp?tab=doc#Reporter
|
||||
package assert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
)
|
||||
|
||||
// DiffReporter is a simple custom reporter that only records differences
|
||||
// detected during comparison.
|
||||
type DiffReporter struct {
|
||||
path cmp.Path
|
||||
diffs []string
|
||||
}
|
||||
|
||||
func (r *DiffReporter) PushStep(ps cmp.PathStep) {
|
||||
r.path = append(r.path, ps)
|
||||
}
|
||||
|
||||
func (r *DiffReporter) Report(rs cmp.Result) {
|
||||
if !rs.Equal() {
|
||||
vx, vy := r.path.Last().Values()
|
||||
r.diffs = append(r.diffs, fmt.Sprintf("%#v:\n\t-: %+v\n\t+: %+v\n", r.path, vx, vy))
|
||||
}
|
||||
}
|
||||
|
||||
func (r *DiffReporter) PopStep() {
|
||||
r.path = r.path[:len(r.path)-1]
|
||||
}
|
||||
|
||||
func (r *DiffReporter) String() string {
|
||||
return strings.Join(r.diffs, "\n")
|
||||
}
|
18
parser.go
Normal file
18
parser.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package editorconfig
|
||||
|
||||
// Parser interface is responsible for the parsing of the ini file and the
|
||||
// globbing patterns.
|
||||
type Parser interface {
|
||||
// ParseIni takes one .editorconfig (ini format) filename and returns its
|
||||
// Editorconfig definition.
|
||||
ParseIni(filename string) (*Editorconfig, error)
|
||||
|
||||
// ParseIni takes one .editorconfig (ini format) filename and returns its
|
||||
// Editorconfig definition. In case of non fatal warnings, they are in
|
||||
// a joined errors and might be ignored in some cases.
|
||||
ParseIniGraceful(filename string) (*Editorconfig, error, error)
|
||||
|
||||
// FnmatchCase takes a pattern, a filename, and tells wether the given filename
|
||||
// matches the globbing pattern.
|
||||
FnmatchCase(pattern string, filename string) (bool, error)
|
||||
}
|
43
simple_parser.go
Normal file
43
simple_parser.go
Normal file
|
@ -0,0 +1,43 @@
|
|||
package editorconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
// SimpleParser implements the Parser interface but without doing any caching.
|
||||
type SimpleParser struct{}
|
||||
|
||||
// ParseInit calls go-ini's Load on the file.
|
||||
func (parser *SimpleParser) ParseIni(filename string) (*Editorconfig, error) {
|
||||
ec, warning, err := parser.ParseIniGraceful(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ec, warning
|
||||
}
|
||||
|
||||
// ParseIni calls go-ini's Load on the file and keep warnings in a separate error.
|
||||
func (parser *SimpleParser) ParseIniGraceful(filename string) (*Editorconfig, error, error) {
|
||||
fp, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, err //nolint:wrapcheck
|
||||
}
|
||||
|
||||
defer fp.Close()
|
||||
|
||||
iniFile, err := ini.Load(fp)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot load %q: %w", filename, err)
|
||||
}
|
||||
|
||||
return newEditorconfig(iniFile)
|
||||
}
|
||||
|
||||
// FnmatchCase calls the module's FnmatchCase.
|
||||
func (parser *SimpleParser) FnmatchCase(selector string, filename string) (bool, error) {
|
||||
return FnmatchCase(selector, filename)
|
||||
}
|
18
testdata/.editorconfig
vendored
Normal file
18
testdata/.editorconfig
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
# http://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
indent_size = 8
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{js,css,less,htm,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
22
testdata/a.ini
vendored
Normal file
22
testdata/a.ini
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
# http://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = false
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.{js,css,less,htm,html}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[invalid]
|
||||
insert_final_newline = off
|
||||
trim_trailing_whitespace = off
|
||||
tab_width = off
|
5
testdata/root/.editorconfig
vendored
Normal file
5
testdata/root/.editorconfig
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
root = true
|
||||
|
||||
[*.go]
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
3
testdata/root/src/.editorconfig
vendored
Normal file
3
testdata/root/src/.editorconfig
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[*.go]
|
||||
indent_size = 4
|
||||
indent_style = tab
|
3
testdata/root/src/a.ini
vendored
Normal file
3
testdata/root/src/a.ini
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[*.go]
|
||||
indent_size = 5
|
||||
indent_style = space
|
7
testdata/root/src/dummy.go
vendored
Normal file
7
testdata/root/src/dummy.go
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
fmt.Println("Dummy file")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue