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
17 changes: 11 additions & 6 deletions .github/workflows/ci-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI and Release

on:
push:
branches:
branches:
- '**'
tags:
- 'v*'
Expand All @@ -24,7 +24,10 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.24'

- name: Tidy modules
run: go mod tidy

- name: Run tests
run: go test -v ./...
Expand All @@ -46,7 +49,10 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.21'
go-version: '1.24'

- name: Tidy modules
run: go mod tidy

- name: Run tests
run: go test -v ./...
Expand Down Expand Up @@ -82,13 +88,12 @@ jobs:
sha256sum * > checksums.txt

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
files: |
dist/*
body_path: ${{ env.RELEASE_NOTES_FILE }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
13 changes: 13 additions & 0 deletions cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"io"
"os"
"runtime/pprof"
"slices"
)

Expand All @@ -16,6 +17,18 @@ func RunCLI(ctx context.Context, args []string, stdin io.Reader, stdout io.Write
return fmt.Errorf("bad parameters: %w", err)
}

if params.CPUProfile != "" {
f, err := os.Create(params.CPUProfile)
if err != nil {
return fmt.Errorf("could not create CPU profile: %w", err)
}
defer f.Close()
if err := pprof.StartCPUProfile(f); err != nil {
return fmt.Errorf("could not start CPU profile: %w", err)
}
defer pprof.StopCPUProfile()
}

replacer, err := replacer(params)
if err != nil {
return fmt.Errorf("cannot parse template: %w", err)
Expand Down
4 changes: 3 additions & 1 deletion cmd/patt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"patt"
)

var version = "dev"

func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer stop()
Expand All @@ -18,4 +20,4 @@ func exitIfErr(err error) {
_, _ = os.Stderr.WriteString(err.Error() + "\n")
os.Exit(1)
}
}
}
42 changes: 0 additions & 42 deletions cmd/profiling/main.go

This file was deleted.

6 changes: 6 additions & 0 deletions params.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ type CLIParams struct {
ReplaceTemplate string
InputFiles []string
Keep bool
CPUProfile string
}

// ParseCLIParams parses flags + positional args
//
//
// patt [flags] search_pattern [[more_search ...] replace_pattern]
// [-- file1 [file2 ...]]
//
Expand Down Expand Up @@ -49,6 +51,10 @@ func ParseCLIParams(argsWithFlags []string) (CLIParams, error) {
}

cmd.Flags().BoolVarP(&out.Keep, "keep", "k", false, "print non‑matching lines")
cmd.Flags().StringVar(&out.CPUProfile, "cpu-profile", "", "write cpu profile to file")
if err := cmd.Flags().MarkHidden("cpu-profile"); err != nil {
return out, err
}

if err := cmd.ParseFlags(argsWithFlags); err != nil {
return out, err
Expand Down
10 changes: 10 additions & 0 deletions params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,16 @@ func TestParseCLIParams_NoErrors(t *testing.T) {
ReplaceTemplate: "template",
},
},
{
name: "cpu profile flag",
args: []string{"--cpu-profile=cpu.pprof", "pattern", "replacement", "--", "input.txt"},
want: CLIParams{
SearchPatterns: []string{"pattern"},
ReplaceTemplate: "replacement",
InputFiles: []string{"input.txt"},
CPUProfile: "cpu.pprof",
},
},
}

for _, tt := range tests {
Expand Down
2 changes: 1 addition & 1 deletion pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,4 @@ func (m *MultiReplacer) Match(line []byte) bool {

func (m *MultiReplacer) Replace(line []byte) []byte {
return m.replacers[m.lastMatchedIx].Replace(line)
}
}
41 changes: 32 additions & 9 deletions pattern/pattern.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ var (
)

type Matcher struct {
e expr
names []string
e expr
names []string
longestLiteral []byte
}

func New(in string) (*Matcher, error) {
Expand All @@ -24,9 +25,18 @@ func New(in string) (*Matcher, error) {
if err := e.validate(); err != nil {
return nil, err
}
var longestLiteral []byte
for _, n := range e {
if l, ok := n.(literals); ok {
if len(l) > len(longestLiteral) {
longestLiteral = l
}
}
}
return &Matcher{
e: e,
names: e.captures(),
e: e,
names: e.captures(),
longestLiteral: longestLiteral,
}, nil
}

Expand All @@ -41,7 +51,15 @@ func ParseLineFilter(in []byte) (*Matcher, error) {
if err = e.validateNoConsecutiveCaptures(); err != nil {
return nil, err
}
return &Matcher{e: e}, nil
var longestLiteral []byte
for _, n := range e {
if l, ok := n.(literals); ok {
if len(l) > len(longestLiteral) {
longestLiteral = l
}
}
}
return &Matcher{e: e, longestLiteral: longestLiteral}, nil
}

func ParseLiterals(in string) ([][]byte, error) {
Expand Down Expand Up @@ -137,9 +155,10 @@ func (m *Matcher) Names() []string {
}

func (m *Matcher) Test(in []byte) bool {
if len(in) == 0 || len(m.e) == 0 {
// An empty line can only match an empty pattern.
return len(in) == 0 && len(m.e) == 0
if len(m.longestLiteral) > 0 {
if !bytes.Contains(in, m.longestLiteral) {
return false
}
}
var off int
for i := range m.e {
Expand All @@ -158,6 +177,10 @@ func (m *Matcher) Test(in []byte) bool {
}
off += j + len(lit)
}
if len(in) == 0 || len(m.e) == 0 {
// An empty line can only match an empty pattern.
return len(in) == 0 && len(m.e) == 0
}
// If we end up on a literal, we only consider the test successful if
// the remaining input is empty. Otherwise, if we end up on a capture,
// the remainder (the captured text) must not be empty.
Expand All @@ -170,4 +193,4 @@ func (m *Matcher) Test(in []byte) bool {
_, reqRem := m.e[len(m.e)-1].(capture)
hasRem := off != len(in)
return reqRem == hasRem
}
}