diff --git a/.gitignore b/.gitignore index e681211..4f3ed05 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /cmd/sre/sre /sre /test.txt +/sre.1 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..65066e3 --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +VERSION = $(shell GOOS=$(shell go env GOHOSTOS) GOARCH=$(shell go env GOHOSTARCH) \ + go run tools/build-version.go) +GOVARS = -X main.Version=$(VERSION) + +build: + go build -trimpath -ldflags "-s -w $(GOVARS)" ./cmd/sre + +install: + go install -trimpath -ldflags "-s -w $(GOVARS)" ./cmd/sre + +sre.1: man/sre.md + pandoc man/sre.md -s -t man -o sre.1 + +package: build sre.1 + mkdir sre-$(VERSION) + cp README.md sre-$(VERSION) + cp LICENSE sre-$(VERSION) + cp sre.1 sre-$(VERSION) + cp sre sre-$(VERSION) + tar -czf sre-$(VERSION).tar.gz sre-$(VERSION) + +clean: + rm -f sre sre.1 sre-*.tar.gz + rm -rf sre-*/ + +.PHONY: build clean install package diff --git a/go.mod b/go.mod index ac75abd..71001a9 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/zyedidia/sre go 1.15 require ( + github.com/blang/semver v3.5.1+incompatible // indirect github.com/jessevdk/go-flags v1.4.0 github.com/mattn/go-shellwords v1.0.11 github.com/zyedidia/gpeg v0.0.0-20210126031808-c49c92955001 diff --git a/go.sum b/go.sum index 2e2f4e9..3aa9219 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,7 @@ github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/blang/semver v1.1.0 h1:ol1rO7QQB5uy7umSNV7VAmLugfLRD+17sYJujRNYPhg= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/mattn/go-shellwords v1.0.11 h1:vCoR9VPpsk/TZFW2JwK5I9S0xdrtUq2bph6/YjEPnaw= diff --git a/man/sre.md b/man/sre.md new file mode 100644 index 0000000..bc422cc --- /dev/null +++ b/man/sre.md @@ -0,0 +1,142 @@ +--- +title: sre +section: 1 +header: SRE Manual +--- + +# NAME + SRE - Structural Regular Expressions Tool + +# SYNOPSIS + sre `[--version] [--help] [OPTIONS] EXPRESSION` + +# DESCRIPTION + SRE is a tool for executing structural regular expressions from the command + line. SRE can be used to operate on streams of data and perform advanced + search and replace. For more information about structural regular + expressions, see Rob Pike's original description at + [http://doc.cat-v.org/bell_labs/structural_regexps/](http://doc.cat-v.org/bell_labs/structural_regexps/). + +# COMMAND LANGUAGE + + In a structural regular expression, regular expressions are composed using + commands to perform tasks like advanced search and replace. A command has an + input string and produces an output string. The following commands are + supported: + +* **`p`**: prints the input string, and then returns the input string. +* **`d`**: returns the empty string. +* **`c//`**: returns the string **``**. +* **`s/

//`**: returns a string where occurrences of the regular expression + **`

`** have been replaced with **``**. +* **`g/

//`**: if **`

`** matches the input, returns the result of **``** + evaluated on the input. Otherwise returns the input with no modification. +* **`v/

//`**: if **`

`** does not match the input, returns the result of + **``** evaluated on the input. Otherwise returns the input with no + modification. +* **`x/

//`**: returns a string where all occurrences of the regular + expression **`

`** have been replaced with the return value of **``** applied + to the particular match. +* **`y/

//`**: returns a string where each part of the string that is not + matched by **``** is replaced by applying **``** to the particular + unmatched string. +* **`u//`**: executes the shell command **``** with the input as stdin and + returns the resulting stdout of the command. Shell commands use a simple + syntax where single or double quotes can be used to group arguments, and + environment variables are accessible with **`$`**. This command is only directly + available as part of the SRE CLI tool. + +The SRE tool also provides an augmentation to the original SRE description from +Pike: command pipelines. A command may be given as **` | | ...`** where +the input of each command is the output of the previous one. + +The syntax follows certain rules, such as using **`/`** as a delimiter. The +backslash (**`\`**) may be used to escape **`/`** or **`\`**, or to create +special characters such as **`\n`**, **`\r`**, or **`\t`**. The syntax also +supports specifying arbitrary bytes using octal, for example **`\14`**. Regular +expressions use the Go syntax described at +[https://golang.org/pkg/regexp/syntax/](https://golang.org/pkg/regexp/syntax/). + +# EXAMPLES + +Most of these examples are from Pike's description, so you can look there for +more detailed explanation. Since `p` is the only command that prints, +technically you must append `| p` to commands that search and replace, because +otherwise nothing will be printed. However, since you will probably forget to +do this, the SRE tool will print the result of the final command before +terminating if there were no calls to `p`. Thus when using the CLI tool you can +omit the `| p` in the following commands and still see the result. + +Print all lines that contain "string": + +``` +x/.*\n/ g/string/p +``` + +Delete all occurrences of "string" and print the result: + +``` +x/string/d | p +``` + +Print all lines containing "rob" but not "robot": + +``` +x/.*\n/ g/rob v/robot/p +``` + +Capitalize all occurrences of the word "i": + +``` +x/[A-Za-z]+/ g/i/ v/../ c/I/ | p +``` + +or (more simply) + +``` +x/[A-Za-z]+/ g/^i$/ c/I/ | p +``` + +Change all occurrences of the complete word "foo" to "bar" except those +occurring in double or single quoted strings: + +``` +y/".*"/ y/'.*'/ x/[a-zA-Z0-9]+/ g/^foo$/ c/bar/ | p +``` + +Replace the complete word "TODAY" with the current date: + +``` +x/[A-Z]+/ g/^TODAY$/ u/date/ | p +``` + +Capitalize all words: + +``` +x/[a-zA-Z]+/ x/^./ u/tr a-z A-Z/ | p +``` + +Note: it is highly recommended that you enclose expressions in single or +double quotes to prevent your shell from interpreting special characters. + +# OPTIONS + `-f, --file` + +: Read input data from file (default: read from stdin). + + `-v, --version` + +: Show version information. + + `-h, --help` + +: Show this help message. + + +# BUGS + +See GitHub Issues: + +# AUTHOR + +Zachary Yedidia diff --git a/tools/build-version.go b/tools/build-version.go new file mode 100644 index 0000000..ca797a5 --- /dev/null +++ b/tools/build-version.go @@ -0,0 +1,76 @@ +package main + +import ( + "fmt" + "os/exec" + "strings" + + "github.com/blang/semver" +) + +func getTag(match ...string) (string, *semver.PRVersion) { + args := append([]string{ + "describe", "--tags", + }, match...) + tag, err := exec.Command("git", args...).Output() + if err != nil { + return "", nil + } + tagParts := strings.Split(string(tag), "-") + if len(tagParts) == 3 { + if ahead, err := semver.NewPRVersion(tagParts[1]); err == nil { + return tagParts[0], &ahead + } + } else if len(tagParts) == 4 { + if ahead, err := semver.NewPRVersion(tagParts[2]); err == nil { + return tagParts[0] + "-" + tagParts[1], &ahead + } + } + + return string(tag), nil +} + +func main() { + if tags, err := exec.Command("git", "tag").Output(); err != nil || len(tags) == 0 { + // no tags found -- fetch them + exec.Command("git", "fetch", "--tags").Run() + } + // Find the last vX.X.X Tag and get how many builds we are ahead of it. + versionStr, ahead := getTag("--match", "v*") + version, err := semver.ParseTolerant(versionStr) + if err != nil { + // no version tag found so just return what ever we can find. + fmt.Println("0.0.0-unknown") + return + } + // Get the tag of the current revision. + tag, _ := getTag("--exact-match") + if tag == versionStr { + // Seems that we are going to build a release. + // So the version number should already be correct. + fmt.Println(version.String()) + return + } + + // If we don't have any tag assume "dev" + if tag == "" || strings.HasPrefix(tag, "nightly") { + tag = "dev" + } + // Get the most likely next version: + if !strings.Contains(version.String(), "rc") { + version.Patch = version.Patch + 1 + } + + if pr, err := semver.NewPRVersion(tag); err == nil { + // append the tag as pre-release name + version.Pre = append(version.Pre, pr) + } + + if ahead != nil { + // if we know how many commits we are ahead of the last release, append that too. + version.Pre = append(version.Pre, *ahead) + } + + fmt.Println(version.String()) +} +