diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f60da40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/* +package*.json +dist/* +reports/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..185a80f --- /dev/null +++ b/Makefile @@ -0,0 +1,217 @@ +# This file is intended as a starting point for a customized makefile for a Go project. +# +# Targets: +# all: Format, check, build, and test the code +# setup: Install build/test toolchain dependencies (e.g. gox) +# lint: Run linters against source code +# bump-{major,minor,patch}: create a new semver git tag +# release-{major,minor,patch}: push a tagged release +# format: Format the source files +# build: Build the command(s) for target OS/arch combinations +# install: Install the command(s) +# clean: Clean the build/test artifacts +# report: Generate build/test reports +# check: Run tests +# bench: Run benchmarks +# dist: zip/tar binaries & documentation +# debug: print parameters +# +# Parameters: +# VERSION: release version in semver format +# BUILD_TAGS: additional build tags to pass to go build +# DISTDIR: path to save distribution files +# RPTDIR: path to save build/test reports +# +# Assumptions: +# - Your package contains a cmd/ package, containing a directory for each produced binary. +# - You have cloc installed and accessible in the PATH. +# - Your GOPATH and GOROOT are set correctly. +# - Your makefile is in the root of your package and does not have a space in its file name. +# - Your root package contains global string variables Version and Build, to receive the bild version number and commit ID, respectively. +# +# Features: +# - report generates files that can be consumed by Jenkins, as well as a list of external dependencies. +# - setup installs all the tools aside from cloc. +# - Works on Windows and with paths containing spaces. +# - Works when executing from outside the makefile directory using -f. +# - Targets are useful both in CI and developer workstations. +# - Handles cross-compiation for multiple OSes and architectures. +# - Bundles binaries and documentation into compressed archives, using tar/gz for Linux and Darwin, and zip for Windows. + + +# Parameters +PKG = code.ndumas.com/ndumas/wikilink-parser +NAME = parse-wikilinks +DOC = README.md LICENSE + + +# Replace backslashes with forward slashes for use on Windows. +# Make is !@#$ing weird. +E := +BSLASH := \$E +FSLASH := / + +# Directories +WD := $(subst $(BSLASH),$(FSLASH),$(shell pwd)) +MD := $(subst $(BSLASH),$(FSLASH),$(shell dirname "$(realpath $(lastword $(MAKEFILE_LIST)))")) +PKGDIR = $(MD) +CMDDIR = $(PKGDIR)/cmd +DISTDIR ?= $(WD)/dist +RPTDIR ?= $(WD)/reports +GP = $(subst $(BSLASH),$(FSLASH),$(GOPATH)) + +# Parameters +VERSION ?= $(shell git -C "$(MD)" describe --tags --dirty=-dev) +COMMIT_ID := $(shell git -C "$(MD)" rev-parse HEAD | head -c8) +BUILD_TAGS ?= release +CMDPKG = $(PKG)/cmd +CMDS := $(shell find "$(CMDDIR)/" -mindepth 1 -maxdepth 1 -type d | sed 's/ /\\ /g' | xargs -n1 basename) +BENCHCPUS ?= 1,2,4 + +# Commands +GOCMD = go +ARCHES ?= amd64 386 +OSES ?= windows linux darwin +OUTTPL = $(DISTDIR)/$(NAME)-$(VERSION)-{{.OS}}_{{.Arch}}/{{.Dir}} +LDFLAGS = -X $(PKG).Version=$(VERSION) -X $(PKG).Build=$(COMMIT_ID) +GOBUILD = gox -osarch="!darwin/386" -rebuild -gocmd="$(GOCMD)" -arch="$(ARCHES)" -os="$(OSES)" -output="$(OUTTPL)" -tags "$(BUILD_TAGS)" -ldflags "$(LDFLAGS)" +GOCLEAN = $(GOCMD) clean +GOINSTALL = $(GOCMD) install -a -tags "$(BUILD_TAGS)" -ldflags "$(LDFLAGS)" +GOTEST = $(GOCMD) test -v -tags "$(BUILD_TAGS)" +DISABLED_LINTERS = varnamelen,interfacer,ifshort,exhaustivestruct,maligned,varcheck,scopelint,structcheck,deadcode,nosnakecase,golint,depguard +GOLINT = golangci-lint run --enable-all --disable "$(DISABLED_LINTERS)" --timeout=30s --tests +GODEP = $(GOCMD) get -d -t +GOFMT = goreturns -w +GOBENCH = $(GOCMD) test -v -tags "$(BUILD_TAGS)" -cpu=$(BENCHCPUS) -run=NOTHING -bench=. -benchmem -outputdir "$(RPTDIR)" +GZCMD = tar -czf +ZIPCMD = zip +SHACMD = sha256sum +SLOCCMD = cloc --by-file --xml --exclude-dir="vendor" --include-lang="Go" +XUCMD = go2xunit +DOCKER_CMD=docker --config ~/.docker/ + +# Dynamic Targets +INSTALL_TARGETS := $(addprefix install-,$(CMDS)) + +.PHONY: all + +all: debug setup dep format lint test bench build dist + +git-push: + git push origin main --tags + git push github main --tags + +release-major: bump-major git-push + +release-minor: bump-minor git-push + +release-patch: bump-patch git-push + + +setup: setup-dirs setup-build setup-format setup-lint setup-reports setup-bump + +setup-bump: + go install github.com/guilhem/bump@latest + +bump-major: setup-bump + bump major + +bump-minor: setup-bump + bump minor + +bump-patch: setup-bump + bump patch + +setup-reports: setup-dirs + go install github.com/tebeka/go2xunit@latest + +setup-build: setup-dirs + go install github.com/mitchellh/gox@latest + +setup-format: setup-dirs + go install github.com/sqs/goreturns@latest + +setup-lint: setup-dirs + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.53.1 + +setup-dirs: + mkdir -p "$(RPTDIR)" + mkdir -p "$(DISTDIR)" + +clean: + $(GOCLEAN) $(PKG) + rm -vrf "$(DISTDIR)"/* + rm -vf "$(RPTDIR)"/* + +format: + $(GOFMT) "$(PKGDIR)" + +dep: + $(GODEP) $(PKG)/... + +lint: setup-dirs dep + $(GOLINT) "$(PKGDIR)" | tee "$(RPTDIR)/lint.out" + +check: setup-dirs clean dep + $(GOTEST) $$(go list "$(PKG)/..." | grep -v /vendor/) | tee "$(RPTDIR)/test.out" + +bench: setup-dirs clean dep + $(GOBENCH) $$(go list "$(PKG)/..." | grep -v /vendor/) | tee "$(RPTDIR)/bench.out" + +report: check + cd "$(PKGDIR)";$(SLOCCMD) --out="$(RPTDIR)/cloc.xml" . | tee "$(RPTDIR)/cloc.out" + cat "$(RPTDIR)/test.out" | $(XUCMD) -output "$(RPTDIR)/tests.xml" + go list -f '{{join .Deps "\n"}}' "$(CMDPKG)/..." | sort | uniq | xargs -I {} sh -c "go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' {} | tee -a '$(RPTDIR)/deps.out'" + +build: $(CMDS) +$(CMDS): setup-dirs dep + $(GOBUILD) "$(CMDPKG)/$@" | tee "$(RPTDIR)/build-$@.out" +install: $(INSTALL_TARGETS) +$(INSTALL_TARGETS): + $(GOINSTALL) "$(CMDPKG)/$(subst install-,,$@)" + +dist: clean build + for docfile in $(DOC); do \ + for dir in "$(DISTDIR)"/*; do \ + cp "$(PKGDIR)/$$docfile" "$$dir/"; \ + done; \ + done + cd "$(DISTDIR)"; for dir in ./*linux*; do $(GZCMD) "$(basename "$$dir").tar.gz" "$$dir"; done + cd "$(DISTDIR)"; for dir in ./*windows*; do $(ZIPCMD) "$(basename "$$dir").zip" "$$dir"; done + cd "$(DISTDIR)"; for dir in ./*darwin*; do $(GZCMD) "$(basename "$$dir").tar.gz" "$$dir"; done + cd "$(DISTDIR)"; find . -maxdepth 1 -type f -printf "$(SHACMD) %P | tee \"./%P.sha\"\n" | sh + $(info "Built v$(VERSION), build $(COMMIT_ID)") + + +.PHONY: docker +docker: docker-image docker-push + +.PHONY: docker-push +docker-push: + $(DOCKER_CMD) tag code.ndumas.com/ndumas/wikilink-parser:$(VERSION) code.ndumas.com/ndumas/wikilink-parser:latest + $(DOCKER_CMD) push code.ndumas.com/ndumas/wikilink-parser:latest + $(DOCKER_CMD) push code.ndumas.com/ndumas/wikilink-parser:$(VERSION) + +.PHONY: docker-image +docker-image: + $(DOCKER_CMD) build --build-arg VERSION=$(VERSION) -t code.ndumas.com/ndumas/wikilink-parser:$(VERSION) . + +.PHONY: build-alpine +build-alpine: +# this version breaks build variable injection +# CGO_ENABLED=0 GOOS=linux go build -ldflags="buildmode=exe $(LDFLAGS) -linkmode external -w -extldflags '-static' " -o $(DISTDIR)/$(NAME)-$(VERSION)-alpine/obp cmd/obp/*.go + CGO_ENABLED=0 GOOS=linux go build -ldflags="$(LDFLAGS)" -o $(DISTDIR)/$(NAME)-$(VERSION)-alpine/obp cmd/obp/*.go + + +debug: + $(info MD=$(MD)) + $(info WD=$(WD)) + $(info PKG=$(PKG)) + $(info PKGDIR=$(PKGDIR)) + $(info DISTDIR=$(DISTDIR)) + $(info VERSION=$(VERSION)) + $(info COMMIT_ID=$(COMMIT_ID)) + $(info BUILD_TAGS=$(BUILD_TAGS)) + $(info CMDS=$(CMDS)) + $(info BUILD_TARGETS=$(BUILD_TARGETS)) + $(info INSTALL_TARGETS=$(INSTALL_TARGETS)) diff --git a/go.mod b/go.mod index 4865887..8fbb4fd 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module wikilinks +module code.ndumas.com/ndumas/wikilink-parser go 1.19 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/lexer.go b/lexer.go new file mode 100644 index 0000000..7a0130a --- /dev/null +++ b/lexer.go @@ -0,0 +1 @@ +package wikilink diff --git a/main.go b/main.go deleted file mode 100644 index 2f35d93..0000000 --- a/main.go +++ /dev/null @@ -1,54 +0,0 @@ -package main - -import ( - "log" - "regexp" -) - -var ( - alias = regexp.MustCompile(`\|(.*)`) - link = regexp.MustCompile(`^([a-zA-Z ]+)?[\|\#\^\\]?`) - subsection = regexp.MustCompile(`#([\w\s]+)`) - block = regexp.MustCompile(`#\^([a-zA-Z ]+)`) - wholeLink = regexp.MustCompile(`\[\[(.*)\]\]`) -) - -type Link struct { - Dest, Alias, Subsection, Block string -} - -func ExtractLink(raw string) Link { - var li Link - - rawLinkMatch := wholeLink.FindAllStringSubmatch(raw, -1) - var rawLink string - if len(rawLinkMatch) > 0 { - rawLink = rawLinkMatch[0][1] - } - - l := link.FindAllStringSubmatch(rawLink, -1) - if len(l) > 0 { - li.Dest = l[0][1] - } - - a := alias.FindAllStringSubmatch(rawLink, -1) - if len(a) > 0 { - li.Alias = a[0][1] - } - - s := subsection.FindAllStringSubmatch(rawLink, -1) - if len(s) > 0 { - li.Subsection = s[0][1] - } - - b := block.FindAllStringSubmatch(rawLink, -1) - if len(b) > 0 { - li.Block = b[0][1] - } - - return li -} - -func main() { - log.Printf("%#v\n", ExtractLink(`Embedding a link in a bigger block of text [[Regular Link#^link to block]] shouldn't cause any problems `)) -} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 38c3b20..0000000 --- a/main_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "testing" -) - -func Test_ExtractLink(t *testing.T) { - tcs := []struct { - name string - in string - expected Link - }{ - {"link", `[[Regular Link]]`, Link{Dest: "Regular Link"}}, - {"transclude+link", `![[Transcluded Link]]`, Link{Dest: "Transcluded Link"}}, - {"link+alias", `[[Regular Link|Alias]]`, Link{Dest: "Regular Link", Alias: "Alias"}}, - {"link+subsection", `[[Regular Link#Subsection of page]]`, Link{Dest: "Regular Link", Subsection: "Subsection of page"}}, - {"link+block", `[[Regular Link#^link to block]]`, Link{Dest: "Regular Link", Block: "link to block"}}, - {"link+subsection+alias", `[[Regular Link#Subsection of page|Alias]]`, Link{Dest: "Regular Link", Subsection: "Subsection of page", Alias: "Alias"}}, - {"link+block+alias", `[[Regular Link#^link to block|Alias]]`, Link{Dest: "Regular Link", Block: "link to block", Alias: "Alias"}}, - {"link+alias+escape", `[[Regular Link\|Alias]]`, Link{Dest: "Regular Link", Alias: "Alias"}}, - {"link+subsection+alias+escape", `[[Regular Link#Subsection of page\|Alias]]`, Link{Dest: "Regular Link", Subsection: "Subsection of page", Alias: "Alias"}}, - {"link+block+alias+escape", `[[Regular Link#^link to block\|Alias]]`, Link{Dest: "Regular Link", Block: "link to block", Alias: "Alias"}}, - } - - for _, tc := range tcs { - t.Run(tc.name, func(t *testing.T) { - out := ExtractLink(tc.in) - if out != tc.expected { - t.Logf("got %#v\n", out) - t.Fail() - } - }) - } -} diff --git a/wikilink.go b/wikilink.go new file mode 100644 index 0000000..ba05d4f --- /dev/null +++ b/wikilink.go @@ -0,0 +1,18 @@ +package wikilink + +import ( +// "log" +// "github.com/13rac1/goldmark-wikilink" +) + +type Wikilink struct { + Link string + Fragment string + Alias string +} + +func Extract(raw string) Wikilink { + var wl Wikilink + + return wl +} diff --git a/wikilink_test.go b/wikilink_test.go new file mode 100644 index 0000000..e4fd37e --- /dev/null +++ b/wikilink_test.go @@ -0,0 +1,52 @@ +package wikilink_test + +import ( + "testing" + + "code.ndumas.com/ndumas/wikilink-parser" +) + +func Test_Extract_Link(t *testing.T) { + t.Parallel() + + tcs := []struct { + name string + in string + expected string + }{ + {name: "wikilink", in: "[[wikilink]]", expected: "wikilink"}, + {name: "wikilink|display name", in: "[[wikilink|display name]]", expected: "wikilink"}, + {name: "wikilink|display name|second pipe", in: "[[wikilink|display name|second pipe]]", expected: "wikilink"}, + {name: "wikilink with numeric alias|420|second pipe", in: "[[wikilink|420|second pipe]]", expected: "wikilink with numeric aliases"}, + {name: "wikilink with spaces in filename", in: "[[wikilink spaces]]", expected: "wikilink"}, + {name: "#heading", in: "[[#heading]]", expected: ""}, + {name: "wikilink#heading", in: "[[wikilink#heading]]", expected: "wikilink"}, + {name: "wikilink#heading|display name", in: "[[wikilink#heading|display name]]", expected: "wikilink"}, + {name: "wikilink#heading|display name", in: "[[wikilink#heading|display name]]", expected: "wikilink"}, + {name: "wikilink with numeric aliases#heading|420|display name", in: "[[wikilink#heading|420|second pipe]]", expected: "wikilink"}, + } + /* + + + "[[#^blockRef]]" + "[[wikilink#^blockRef]]" + "[[wikilink#^blockRef|display name]]" + "[[wikilink#^blockRef|display name|secondpipe]]" + + "[[#^blockRef]]" + "[[wikilink#^blockRef]]" + "[[wikilink#^blockRef|display name|secondpipe]]" + */ + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + out := wikilink.Extract(tc.in) + if out.Link != tc.expected { + t.Logf("got %#v\n", out) + t.Fail() + } + }) + } +}