Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# .github/workflows/release.yml
name: Release

on:
push:
tags:
- "v*"
branches:
- main
pull_request:
branches:
- main

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-go@v4
with:
go-version: "1.25"

- name: Set version
id: version
run: |
if [[ "${{ startsWith(github.ref, 'refs/tags/') }}" == "true" ]]; then
echo "version=${{ github.ref_name }}" >> $GITHUB_OUTPUT
else
VERSION=$(cat VERSION)
echo "version=v${VERSION}" >> $GITHUB_OUTPUT
fi

- uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: v1.26.2
args: ${{ startsWith(github.ref, 'refs/tags/') && 'release' || 'build --snapshot' }} --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GORELEASER_CURRENT_TAG: ${{ steps.version.outputs.version }}
26 changes: 26 additions & 0 deletions .goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
builds:
- env:
- CGO_ENABLED=0
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm64
ldflags:
- -s -w -X main.Version={{.Version}}

archives:
- format: tar.gz
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}

release:
github:
owner: digitalstudium
name: helmfmt
113 changes: 111 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ These Go-template tags are indented:

These are not indented:

- `tpl` and `toYaml` because they can break YAML indentation
- `tpl`, `template` and `toYaml` because they can break YAML indentation

---

Expand Down Expand Up @@ -107,6 +107,9 @@ Download it from [releases](https://github.com/digitalstudium/helmfmt/releases)
```bash
helmfmt <chart-path>
helmfmt --files <file1> <file2> ...
helmfmt --files <file1> <file2> ... --stdout
helmfmt --disable-indent=template,include <chart-path>
helmfmt --enable-indent=toYaml --files <file1> <file2> ...
```

Example run:
Expand All @@ -127,14 +130,120 @@ Processed: 2, Updated: 1, Errors: 0

---

## Configuration

`helmfmt` can be configured using a `.helmfmt` file in JSON format. The tool looks for configuration files in this order:

1. `./.helmfmt` (project directory)
2. `~/.helmfmt` (home directory)

### Default Configuration

```json
{
"indent_size": 2,
"extensions": [".yaml", ".yml", ".tpl"],
"rules": {
"indent": {
"tpl": {
"disabled": true,
"exclude": []
},
"toYaml": {
"disabled": true,
"exclude": []
},
"template": {
"disabled": false,
"exclude": []
},
"printf": {
"disabled": false,
"exclude": []
},
"include": {
"disabled": false,
"exclude": []
},
"fail": {
"disabled": false,
"exclude": []
}
}
}
}
```

### Rule Configuration

Each rule can be configured with:

- **`disabled`**: Set to `true` to disable the rule entirely
- **`exclude`**: Array of file patterns to exclude from this rule

### Example Configurations

**Enable `tpl` and `toYaml` indentation:**

```json
{
"rules": {
"indent": {
"tpl": {
"disabled": false
},
"toYaml": {
"disabled": false
}
}
}
}
```

**Exclude test files from `include` indentation:**

```json
{
"rules": {
"indent": {
"include": {
"exclude": ["tests/*", "**/test-*.yaml"]
}
}
}
}
```

**Use 4 spaces for indentation:**

```json
{
"indent_size": 4
}
```

### Command-line Rule Overrides

You can override configuration rules using command-line flags:

```bash
# Disable specific rules
helmfmt --disable-indent=template,include <chart-path>

# Enable specific rules (overrides config file)
helmfmt --enable-indent=tpl,toYaml <chart-path>
```

---

## pre-commit hook configuration

To use `helmfmt` as a pre-commit hook, add the following to your `.pre-commit-config.yaml`:

```yaml
repos:
- repo: https://github.com/digitalstudium/helmfmt
rev: v0.1.1
rev: v0.2.0
hooks:
- id: helmfmt
```
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.2.0
74 changes: 54 additions & 20 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"path/filepath"
"regexp"
"strings"
"text/template"
Expand All @@ -11,8 +12,6 @@ import (
_ "helm.sh/helm/v3/pkg/engine" // Import to work with Helm's private functions (via go linkname)
)

const indentStep = 2 // Number of spaces per indentation level

// Типы токенов для ясности, что мы нашли
type tokenKind int

Expand All @@ -37,7 +36,6 @@ var (
controlRe = regexp.MustCompile(`^\s*(if|range|with|define|block)\b`)
elseRe = regexp.MustCompile(`^\s*else\b`)
endRe = regexp.MustCompile(`^\s*end\b`)
simpleRe = regexp.MustCompile(`^\s*(include|fail|printf)\b`)
endInLineRe = regexp.MustCompile(`\{\{(-?)(\s*)end\b[^}]*(-?)\}\}`)

// Для извлечения первого слова
Expand All @@ -64,7 +62,7 @@ func validateTemplateSyntax(src string) error {
}

// Главная функция выравнивания
func formatIndentation(src string) string {
func formatIndentation(src string, config *Config, filePath string) string {
lines := strings.Split(src, "\n")
depth := 0

Expand All @@ -81,11 +79,23 @@ func formatIndentation(src string) string {
_, _, _ = skipLeadingBlockComment(lines, i)
}

_, _, endLine, kind, found := getTokenAtLineStartSkippingLeadingComments(lines, i)
keyword, _, endLine, kind, found := getTokenAtLineStartSkippingLeadingComments(lines, i, config)
if !found {
continue
}

// Check if we should skip indenting this simple function
if kind == tokSimple {
ruleName := getRuleName(keyword, kind)
if ruleName != "" {
rule := config.Rules.Indent[ruleName] // Updated access pattern
if rule.Disabled || matchesExcludePattern(filePath, rule.Exclude) {
i = endLine
continue // Skip indenting this token
}
}
}

// Вычисляем уровень отступа
level := depth
if kind == tokElse || kind == tokEnd {
Expand All @@ -94,15 +104,13 @@ func formatIndentation(src string) string {
}
}

indent := strings.Repeat(" ", level*indentStep)

// Применяем отступ ко всем строкам, начиная с комментария (если есть)
actualStartLine := commentStart
for j := actualStartLine; j <= endLine && j < len(lines); j++ {
// Apply indentation
indent := strings.Repeat(" ", level*config.IndentSize)
for j := commentStart; j <= endLine && j < len(lines); j++ {
lines[j] = indent + strings.TrimLeft(lines[j], " \t")
}

// Меняем глубину после обработки текущего тега
// Always update depth for control structures
switch kind {
case tokControlOpen:
depth++
Expand All @@ -119,7 +127,7 @@ func formatIndentation(src string) string {
}

// Ищем токен в начале строки, пропуская ведущий комментарий
func getTokenAtLineStartSkippingLeadingComments(lines []string, start int) (keyword string, startLine int, endLine int, kind tokenKind, found bool) {
func getTokenAtLineStartSkippingLeadingComments(lines []string, start int, config *Config) (keyword string, startLine int, endLine int, kind tokenKind, found bool) {
i := start

for {
Expand Down Expand Up @@ -153,16 +161,16 @@ func getTokenAtLineStartSkippingLeadingComments(lines []string, start int) (keyw
}

// Парсим токен из остатка строки
return parseTokenFromLine(lines, ci, remainder)
return parseTokenFromLine(lines, ci, remainder, config)
}

// Парсим токен напрямую
return parseTokenFromLine(lines, i, line)
return parseTokenFromLine(lines, i, line, config)
}
}

// Парсинг токена из строки
func parseTokenFromLine(lines []string, lineIdx int, line string) (keyword string, startLine int, endLine int, kind tokenKind, found bool) {
func parseTokenFromLine(lines []string, lineIdx int, line string, config *Config) (keyword string, startLine int, endLine int, kind tokenKind, found bool) {
// Извлекаем содержимое после {{ или {{-
match := tagOpenRe.FindStringSubmatch(line)
if match == nil {
Expand Down Expand Up @@ -198,12 +206,16 @@ func parseTokenFromLine(lines []string, lineIdx int, line string) (keyword strin
end := findTagEndMultiline(lines, lineIdx, line)
return "end", lineIdx, end, tokEnd, true

case simpleRe.MatchString(content):
matches := simpleRe.FindStringSubmatch(content)
keyword := matches[1]
default:
// Check if first word has a rule defined - if so, treat as simple
if matches := firstWordRe.FindStringSubmatch(content); matches != nil {
keyword := matches[1]

end := findTagEndMultiline(lines, lineIdx, line)
return keyword, lineIdx, end, tokSimple, true
if _, hasRule := config.Rules.Indent[keyword]; hasRule { // Updated access pattern
end := findTagEndMultiline(lines, lineIdx, line)
return keyword, lineIdx, end, tokSimple, true
}
}
}

return "", lineIdx, lineIdx, tokNone, false
Expand Down Expand Up @@ -288,3 +300,25 @@ func hasEndInRange(lines []string, start, end int) bool {
}
return false
}

func matchesExcludePattern(filePath string, patterns []string) bool {
for _, pattern := range patterns {
// Convert glob pattern to regex
if matched, _ := filepath.Match(pattern, filePath); matched {
return true
}

// Also support regex patterns
if regexp.MustCompile(pattern).MatchString(filePath) {
return true
}
}
return false
}

func getRuleName(keyword string, kind tokenKind) string {
if kind == tokSimple {
return keyword // Just return the keyword (printf, include, fail)
}
return ""
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/digitalstudium/helmfmt
go 1.25.1

require (
github.com/spf13/cobra v1.10.1
gopkg.in/yaml.v3 v3.0.1
helm.sh/helm/v3 v3.19.0
)
Expand All @@ -26,6 +27,7 @@ require (
github.com/google/gnostic-models v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
Expand All @@ -38,6 +40,7 @@ require (
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.9 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
Expand Down
Loading