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())
+}
+