Skip to content

Commit 3e2c20a

Browse files
committed
MAJOR: add aspell as additional check for spelling errors
1 parent f74106a commit 3e2c20a

17 files changed

+696
-94
lines changed

.aspell.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
mode: all
2+
ignore:
3+
- go.mod
4+
- go.sum
5+
- .aspell.yml
6+
- '*_test.go'
7+
allowed:
8+
- aspell
9+
- repo
10+
- yaml
11+
- config
12+
- Github
13+
- Gitlab
14+
- env
15+
- failsafe
16+
- OPTIM
17+
- golang
18+
- mkdir
19+
- WORKDIR
20+
- apk
21+
- ENTRYPOINT
22+
- ubuntu
23+
- golangci
24+
- splitted
25+
- sudo
26+
- mri
27+
- IID
28+
- repo

.github/workflows/actions.yaml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@ jobs:
66
runs-on: ubuntu-latest
77
steps:
88
- uses: actions/checkout@v4
9+
- name: Set up Go
10+
uses: actions/setup-go@v4
11+
with:
12+
go-version-file: 'go.mod'
13+
check-latest: true
914
- name: golangci-lint
10-
uses: golangci/golangci-lint-action@v3
15+
uses: golangci/golangci-lint-action@v6
1116
go_build:
1217
name: Go build
1318
runs-on: ubuntu-latest
@@ -32,6 +37,8 @@ jobs:
3237
runs-on: ubuntu-latest
3338
needs: ["go_build"]
3439
steps:
40+
- name: Install Aspell
41+
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
3542
- uses: actions/checkout@v4
3643
- name: Set up Go
3744
uses: actions/setup-go@v4
@@ -51,6 +58,8 @@ jobs:
5158
runs-on: ubuntu-latest
5259
needs: ["go_build"]
5360
steps:
61+
- name: Install Aspell
62+
run: sudo apt-get update && sudo apt-get install -y aspell aspell-en
5463
- uses: actions/checkout@v4
5564
with:
5665
fetch-depth: 0

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
FROM golang:alpine as builder
1+
FROM golang:alpine AS builder
22
RUN mkdir /build
33
ADD . /build/
44
WORKDIR /build
55
RUN go build -o check
66

77
FROM alpine:latest
8+
RUN apk --no-cache add aspell aspell-en
89
COPY --from=builder /build/check /check
910
WORKDIR /
1011
ENTRYPOINT ["/check"]

README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ bug: fix set-var parsing bug in config-parser
3232
```
3333
BUG/MEDIUM: fix set-var
3434
```
35-
- Unkown severity
35+
- Unknown severity
3636
```
3737
BUG/MODERATE: fix set-var parsing bug in config-parser
3838
```
@@ -95,3 +95,31 @@ TagOrder:
9595
### Optional parameters
9696

9797
The program accepts an optional parameter to specify the location (path) of the base of the git repository. This can be useful in certain cases where the checked-out repo is in a non-standard location within the CI environment, compared to the running path from which the check-commit binary is being invoked.
98+
99+
### aspell
100+
101+
to check also spellcheck errors aspell was added. it can be configured with `.aspell.yml`
102+
103+
example
104+
```yaml
105+
mode: subject
106+
ignore:
107+
- go.mod
108+
- go.sum
109+
- *test.go
110+
allowed:
111+
- aspell
112+
- config
113+
```
114+
115+
mode can be set as
116+
117+
- `subject`
118+
- `default` option
119+
- only subject of commit message will be checked
120+
- `commit`
121+
- whole commit message will be checked
122+
- `all`
123+
- both commit message and all code committed
124+
- `disabled`
125+
- check is disabled

aspell/aspell.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package aspell
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"os/exec"
8+
"slices"
9+
"strings"
10+
11+
"check-commit/match"
12+
13+
"github.com/fatih/camelcase"
14+
)
15+
16+
type Aspell struct {
17+
Mode mode `yaml:"mode"`
18+
Ignore []string `yaml:"ignore"`
19+
AllowedWords []string `yaml:"allowed"`
20+
HelpText string `yaml:"-"`
21+
}
22+
23+
var (
24+
camelCaseOK = map[string]struct{}{
25+
"HAProxy": {},
26+
"golang": {},
27+
}
28+
camelCaseNotOK = map[string]struct{}{}
29+
)
30+
31+
func (a Aspell) checkSingle(data string, allowedWords []string) error {
32+
var words []string
33+
var badWords []string
34+
35+
checkRes, err := checkWithAspellExec(data)
36+
if checkRes != "" {
37+
words = strings.Split(checkRes, "\n")
38+
}
39+
if err != nil {
40+
return err
41+
}
42+
43+
for _, word := range words {
44+
wordLower := strings.ToLower(word)
45+
if len(word) < 1 {
46+
continue
47+
}
48+
if _, ok := camelCaseNotOK[wordLower]; ok {
49+
badWords = append(badWords, wordLower)
50+
continue
51+
}
52+
if _, ok := camelCaseOK[wordLower]; ok {
53+
continue
54+
}
55+
if slices.Contains(a.AllowedWords, wordLower) || slices.Contains(allowedWords, wordLower) {
56+
continue
57+
}
58+
splitted := camelcase.Split(word)
59+
if len(splitted) > 1 {
60+
for _, s := range splitted {
61+
er := a.checkSingle(s, allowedWords)
62+
if er != nil {
63+
camelCaseNotOK[wordLower] = struct{}{}
64+
badWords = append(badWords, word+":"+s)
65+
break
66+
}
67+
}
68+
} else {
69+
camelCaseNotOK[wordLower] = struct{}{}
70+
badWords = append(badWords, word)
71+
}
72+
}
73+
74+
if len(badWords) > 0 {
75+
return fmt.Errorf("aspell: %s", badWords)
76+
}
77+
return nil
78+
}
79+
80+
func (a Aspell) Check(subjects []string, commitsFull []string, content []map[string]string) error {
81+
var response string
82+
var checks []string
83+
switch a.Mode {
84+
case modeDisabled:
85+
return nil
86+
case modeSubject:
87+
checks = subjects
88+
case modeCommit:
89+
checks = commitsFull
90+
case modeAll:
91+
for _, file := range content {
92+
for name, v := range file {
93+
nextFile := false
94+
for _, filter := range a.Ignore {
95+
if match.MatchFilter(name, filter) {
96+
// log.Println("File", name, "in ignore list")
97+
nextFile = true
98+
continue
99+
}
100+
}
101+
if nextFile {
102+
continue
103+
}
104+
var imports []string
105+
if strings.HasSuffix(name, ".go") {
106+
imports = match.GetImportWordsFromGoFile(name)
107+
}
108+
if err := a.checkSingle(v, imports); err != nil {
109+
log.Println("File", name, err.Error())
110+
response += fmt.Sprintf("%s\n", err)
111+
}
112+
}
113+
}
114+
checks = []string{}
115+
default:
116+
checks = subjects
117+
}
118+
119+
for _, subject := range checks {
120+
if err := a.checkSingle(subject, []string{}); err != nil {
121+
response += fmt.Sprintf("%s\n", err)
122+
}
123+
}
124+
125+
if len(response) > 0 {
126+
return fmt.Errorf("%s", response)
127+
}
128+
return nil
129+
}
130+
131+
func checkWithAspellExec(subject string) (string, error) {
132+
cmd := exec.Command("aspell", "--list")
133+
cmd.Stdin = strings.NewReader(subject)
134+
135+
var stdout, stderr bytes.Buffer
136+
cmd.Stdout = &stdout
137+
cmd.Stderr = &stderr
138+
err := cmd.Run()
139+
if err != nil {
140+
log.Printf("aspell error: %s, stderr: %s", err, stderr.String())
141+
return "", err
142+
}
143+
144+
return stdout.String() + stderr.String(), nil
145+
}

aspell/aspell_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package aspell
2+
3+
import "testing"
4+
5+
func Test_checkWithAspell(t *testing.T) {
6+
aspell := Aspell{
7+
Mode: modeSubject,
8+
AllowedWords: []string{"config"},
9+
}
10+
tests := []struct {
11+
name string
12+
subject string
13+
wantErr bool
14+
}{
15+
{"OK 1", "BUG/MEDIUM: config: add default location of path to the configuration file", false},
16+
{"OK 2", "BUG/MEDIUM: config: add default location of path to the configuration file xtra", false},
17+
{"error - flie", "BUG/MEDIUM: config: add default location of path to the configuration flie", true},
18+
{"error - locatoin", "CLEANUP/MEDIUM: config: add default locatoin of path to the configuration file", true},
19+
{"error - locatoin+flie", "CLEANUP/MEDIUM: config: add default locatoin of path to the configuration flie", true},
20+
}
21+
for _, tt := range tests {
22+
t.Run(tt.name, func(t *testing.T) {
23+
err := aspell.checkSingle(tt.subject, []string{"xtra"})
24+
if tt.wantErr && err == nil {
25+
t.Errorf("checkWithAspell() error = %v, wantErr %v", err, tt.wantErr)
26+
}
27+
if !tt.wantErr && err != nil {
28+
t.Errorf("checkWithAspell() error = %v, wantErr %v", err, tt.wantErr)
29+
}
30+
})
31+
}
32+
}

aspell/mode.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package aspell
2+
3+
type mode string
4+
5+
const (
6+
modeDisabled mode = "disabled"
7+
modeSubject mode = "subject"
8+
modeCommit mode = "commit"
9+
modeAll mode = "all"
10+
)

aspell/new.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package aspell
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
8+
"gopkg.in/yaml.v3"
9+
)
10+
11+
func New(filename string) (Aspell, error) {
12+
var data []byte
13+
var err error
14+
if data, err = os.ReadFile(filename); err != nil {
15+
log.Printf("warning: aspell exceptions file not found (%s)", err)
16+
}
17+
18+
var aspell Aspell
19+
err = yaml.Unmarshal(data, &aspell)
20+
if err != nil {
21+
return Aspell{}, err
22+
}
23+
24+
switch aspell.Mode {
25+
case modeDisabled:
26+
case modeSubject:
27+
case modeCommit:
28+
case modeAll:
29+
case "":
30+
aspell.Mode = modeSubject
31+
default:
32+
return Aspell{}, fmt.Errorf("invalid mode: %s", aspell.Mode)
33+
}
34+
35+
log.Printf("aspell mode set to %s", aspell.Mode)
36+
aspell.HelpText = `aspell can be configured with .aspell.yml file.
37+
content example:
38+
mode: subject
39+
ignore:
40+
- go.mod
41+
- go.sum
42+
- .aspell.yml
43+
allowed:
44+
- aspell
45+
- config
46+
`
47+
return aspell, nil
48+
}

aspell_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"testing"
7+
8+
"check-commit/aspell"
9+
)
10+
11+
func Test_checkWithAspell(t *testing.T) {
12+
aspell, err := aspell.New(".aspell.yml")
13+
if err != nil {
14+
t.Errorf("checkWithAspell() error = %v", err)
15+
}
16+
17+
// readmeFile, err := os.Open("README.md")
18+
readmeFile, err := os.Open("match/golang.go")
19+
if err != nil {
20+
t.Errorf("could not open README.md file: %v", err)
21+
}
22+
defer readmeFile.Close()
23+
24+
scanner := bufio.NewScanner(readmeFile)
25+
readme := ""
26+
for scanner.Scan() {
27+
readme += scanner.Text() + "\n"
28+
}
29+
if err := scanner.Err(); err != nil {
30+
t.Errorf("could not read README.md file: %v", err)
31+
}
32+
err = aspell.Check([]string{"subject"}, []string{"body"}, []map[string]string{
33+
//{"README.md": readme},
34+
{"match/golang.go": readme},
35+
})
36+
if err != nil {
37+
t.Errorf("checkWithAspell() error = %v", err)
38+
}
39+
}

0 commit comments

Comments
 (0)