Compare commits

...

73 Commits

Author SHA1 Message Date
Nick Dumas e95c747899 restructuring subcommands for hugo tooling
Nick Dumas add10c2d1a Hugo bundle directory scaffolding
Nick Dumas 0dda41e3f8 Namespacing your viper binds is important
Nick Dumas b631b717b6 debugging viper
Nick Dumas 93fa411750 saving my work
Nick Dumas 4f76a1d571 First step: inventory posts in provided directory
Nick Dumas 94701a01b2 Cleaning up dockerfile
Nick Dumas 3252693509 more hugo command drafting
Nick Dumas 7271830829 drafting the hugo subcommand
Nick Dumas cff10a36ca starting on hugo command
Nick Dumas f479ba4d4c Proper error handling for the walkfunc
Nick Dumas 72d5ed9d53 This is the happy case.
Nick Dumas 56f806a1f5 No more contextless error returns
Nick Dumas 8de6452c64 refactoring makefile, adding error detail
Nick Dumas 5813113d9f Not sure why I changed this return statement
Nick Dumas 3036a6a717 Cleaning up copy command
Nick Dumas 84aa6ce9ca Now the alpine build injects build info
Nick Dumas 755c213b91 Alpine build works, without version injection
Nick Dumas 2524fd772a more convenience tools for releasing
Nick Dumas 78fb258b5d Reordering target definitions
Nick Dumas d8d58acc36 Making version tagging easier
Nick Dumas 869ca7b52a adding a positive test case
Nick Dumas 06d7bc3c56 Only build docker images on tags
Nick Dumas 5c48fe6b22 Unit tests and linting cleanup
Nick Dumas 13e1abd65a more condensing
Nick Dumas 0dd2d0dae3 condensing more stuff together for sanity/cleanup
Nick Dumas 60e96cce18 Now that's what i call linting
Nick Dumas 54e60b113e also working on unit tests
Nick Dumas 1dcd39afa3 Starting big time cleanup
Nick Dumas 230d8b2160 Adding some failing tests
Nick Dumas 0aec42aceb gotta run setup for the tests
Nick Dumas 0bea3ce6d0 updating to match new makefile
Nick Dumas c91faf4ca0 Don't need that debug statement anymore
Nick Dumas 4f1e52731c duplicate step name
Nick Dumas 270922f330 We don't actually have a git repo
Nick Dumas c366f8989f more debug
Nick Dumas 0709289a02 debug time
Nick Dumas 11f7b5362f this variable doesn't get set by drone
Nick Dumas 2188af0b78 successfully automated tagged+versioned docker image builds and pushes
Nick Dumas 135591cd0b before things get out of hand
Nick Dumas 04919ea208 Finally got versions baked into the builds
Nick Dumas 7c38d9ae71 trying to implement a version command
Nick Dumas b22519cd96 Adding version and tag info for the build to inject
Nick Dumas 70d0453120 ignore reports
Nick Dumas cf4d107c98 builds work, but i need to adjust the matrix
Nick Dumas d2c47e82b5 update gitignore
Nick Dumas 468fd8ae1b Modernizing the go invocations
Nick Dumas cfa4465027 Upgrading the makefile in a major way
Nick Dumas 47c6d2ac0e such a simple typo
Nick Dumas 36832f9bb2 Quoting variables is important too I guess
Nick Dumas 6622f5ef46 debug time
Nick Dumas d64d913602 debug time
Nick Dumas 7d6af47591 Maybe I do need to ditch ~
Nick Dumas 8e8a6a9ebd whoops, gotta use the right secret
Nick Dumas 75ec3dd05a single character typos will get you
Nick Dumas 75f33bc0e4 removing this, don't need it if i have credentials file
Nick Dumas 991b6f6e04 maybe ~ doesn't resolve properly
Nick Dumas 061a59b428 ????
Nick Dumas a3244c3684 Refactoring the makefile for sanity and pipelining
Nick Dumas 79bcaa4eb4 Adding phonies
Nick Dumas 5201d0a4d5 Secret token time?
Nick Dumas 055ceea0ad the token seems to be wrong but this should be better
Nick Dumas 2c858460fe still can't read
Nick Dumas a06b400ee4 reading the docs is hard
Nick Dumas 65ad2bf182 fucking tabs
Nick Dumas 8b734ac9f6 hopefully authenticating into the container registry
Nick Dumas 8d53d196ad I need docker, I guess
Nick Dumas 25dfb1aae1 install some tooling
Nick Dumas 3e38783817 better naming for steps
Nick Dumas 2504e6d5fa first pass at the drone pipeline
Nick Dumas 0bf889b60a Docker builds and makefile to match
Nick Dumas 93869409c3 correcting workdir value
Nick Dumas 8156b60f03 this IS the final piece

@ -0,0 +1,43 @@
kind: pipeline
name: publish-docker-image
clone:
disable: true
steps:
- name: clone
image: alpine/git
commands:
- git clone https://code.ndumas.com/ndumas/obsidian-pipeline.git .
- name: build-and-public-docker-image
image: docker:dind
volumes:
- name: dockersock
path: /var/run
commands:
- apk add git docker make
- mkdir $HOME/.docker/
- echo "$GITEA_DOCKER_REGISTRY_TOKEN" > $HOME/.docker/config.json
- make debug
- make docker
environment:
GITEA_DOCKER_REGISTRY_TOKEN:
from_secret: GITEA_DOCKER_REGISTRY_TOKEN
services:
- name: docker
image: docker:dind
privileged: true
volumes:
- name: dockersock
path: /var/run
volumes:
- name: dockersock
temp: {}
trigger:
ref:
include:
- refs/tags/*

@ -21,7 +21,7 @@ jobs:
with:
go-version: ">=1.20.0"
- name: Test
run: make test
run: make setup && make check
goreleaser:
needs: test
@ -40,7 +40,7 @@ jobs:
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
with:
workdir: "./cmd/obp/cmd/"
workdir: "./cmd/obp/"
distribution: goreleaser
version: ${{ env.GITHUB_REF_NAME }}
args: release --clean

3
.gitignore vendored

@ -1,3 +1,4 @@
node_modules/*
package*.json
bin/*
dist/*
reports/*

@ -1,4 +1,4 @@
builds:
id: "obp-release"
main: "./cmd/obp/cmd/"
main: "./cmd/obp/"
binary: "obp"

@ -0,0 +1,37 @@
# syntax=docker/dockerfile:1
# Build the application from source
FROM golang:latest AS build-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
# This definitely works
# RUN CGO_ENABLED=0 GOOS=linux go build -o obp ./cmd/obp/
RUN make build-alpine
# Deploy the application binary into a lean image
FROM alpine:latest AS build-release-stage
WORKDIR /
ARG VERSION=version
COPY --from=build-stage /app/dist/*-alpine/obp /bin/
RUN chmod +x /bin/obp
ARG USER=default
ENV HOME /home/$USER
RUN apk add --update sudo
RUN adduser -D $USER \
&& echo "$USER ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$USER \
&& chmod 0440 /etc/sudoers.d/$USER
USER $USER
WORKDIR $HOME

@ -1,18 +1,217 @@
BINARY_NAME=obp
# 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.
build-all:
GOOS=darwin GOARCH=amd64 go build -o bin/${BINARY_NAME}-darwin cmd/obp/*.go
GOOS=linux GOARCH=amd64 go build -o bin/${BINARY_NAME}-linux cmd/obp/*.go
GOOS=windows GOARCH=amd64 go build -o bin/${BINARY_NAME}-windows.exe cmd/obp/*.go
clean-all:
go clean
rm bin/${BINARY_NAME}-darwin
rm bin/${BINARY_NAME}-linux
rm bin/${BINARY_NAME}-windows.exe
# Parameters
PKG = code.ndumas.com/ndumas/obsidian-pipeline
NAME = obsidian-pipeline
DOC = README.md LICENSE
test:
go test ./...
test_coverage:
go test ./... -coverprofile=coverage.out
# 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/obsidian-pipeline:$(VERSION) code.ndumas.com/ndumas/obsidian-pipeline:latest
$(DOCKER_CMD) push code.ndumas.com/ndumas/obsidian-pipeline:latest
$(DOCKER_CMD) push code.ndumas.com/ndumas/obsidian-pipeline:$(VERSION)
.PHONY: docker-image
docker-image:
$(DOCKER_CMD) build --build-arg VERSION=$(VERSION) -t code.ndumas.com/ndumas/obsidian-pipeline:$(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))

@ -1,11 +0,0 @@
package obp
func (p *Pipeline) FindAttachments() error {
return nil
}
func (p *Pipeline) MoveAttachments(post string) error {
return nil
}

@ -1,142 +0,0 @@
package obp
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"go.uber.org/zap"
)
func (p *Pipeline) Walk() error {
notesRoot := os.DirFS(p.Source)
blogRoot := os.DirFS(p.Target)
err := fs.WalkDir(notesRoot, ".", p.findAttachments)
if err != nil {
return fmt.Errorf("error scanning for attachments: %w", err)
}
err = fs.WalkDir(notesRoot, ".", p.findNotes)
if err != nil {
return fmt.Errorf("error scanning vault for posts: %w", err)
}
err = fs.WalkDir(blogRoot, ".", p.findPosts)
if err != nil {
return fmt.Errorf("error scanning blog for posts: %w", err)
}
return nil
}
func (p *Pipeline) findNotes(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindNotes").With(zap.String("path", path))
if strings.HasSuffix(path, ".md") && strings.Contains(path, p.BlogDir) {
walkLogger.Info("found blog post to publish, adding to index")
p.Notes = append(p.Notes, path)
}
return nil
}
func (p *Pipeline) findAttachments(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindAttachments").With(zap.String("path", path))
if strings.Contains(path, p.AttachmentsDir) {
walkLogger.Info("found attachment file, adding to index")
absPath, err := filepath.Abs(filepath.Join(p.Source, path))
if err != nil {
return fmt.Errorf("error generating absolute path for attachment %q: %w", path, err)
}
walkLogger.Info("adding Attachment",
zap.String("key", filepath.Base(absPath)),
zap.String("value", absPath),
)
p.Attachments[filepath.Base(absPath)] = absPath
}
return nil
}
func (p *Pipeline) findPosts(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindPosts").With(zap.String("path", path))
if strings.HasSuffix(path, "index.md") {
walkLogger.Info("found index.md, adding to index")
p.Posts = append(p.Posts, path)
}
return nil
}
func (p *Pipeline) Move() error {
moveLogger := p.L.Named("Move")
moveLogger.Info("scanning posts", zap.Strings("posts", p.Posts))
for _, post := range p.Notes {
// log.Printf("scanning %q for attachment links", post)
linkedAttachments, err := extractAttachments(filepath.Join(p.Source, post))
if err != nil {
return fmt.Errorf("could not extract attachment links from %q: %w", post, err)
}
for _, attachment := range linkedAttachments {
att, ok := p.Attachments[attachment]
if !ok {
return fmt.Errorf("Attachment is linked by post %q but doesn't exist in attachments directory %q", post, p.AttachmentsDir)
}
moveAttachment(post, att, p.L.Named("moveAttachment"))
}
}
return nil
}
func moveAttachment(post, attachment string, l *zap.Logger) error {
l.Info("moving attachment",
zap.String("post", post),
zap.String("attachment", attachment),
)
return nil
}
func extractAttachments(post string) ([]string, error) {
pat := regexp.MustCompile(`\[\[Resources\/attachments\/(.*)?\]\]`)
attachments := make([]string, 0)
postBody, err := ioutil.ReadFile(post)
if err != nil {
return attachments, fmt.Errorf("error opening post to scan for attachment links: %w", err)
}
for _, att := range pat.FindAllSubmatch(postBody, -1) {
filename := string(att[1])
attachments = append(attachments, filename)
}
return attachments, nil
}

@ -4,40 +4,15 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
package cmd
import (
// "fmt"
"github.com/spf13/cobra"
)
var (
source, target string
)
// rootCmd represents the base command when called without any subcommands
var hugoCmd = &cobra.Command{
Use: "hugo",
Short: "convert a set of Obsidian notes into a Hugo compatible directory structure",
Long: `long description`,
PreRunE: func(cmd *cobra.Command, args []string) error {
// here is where I validate arguments, open and parse config files, etc
return nil
},
Short: "manage your hugo blog using your vault as a source of truth",
Long: `manage your hugo blog using your vault as a source of truth`,
}
func init() {
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
hugoCmd.PersistentFlags().StringVar(&source, "source", "", "directory containing ready-to-publish posts")
hugoCmd.PersistentFlags().StringVar(&target, "target", "", "target Hugo directory (typically content/posts)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
// rootCmd.SetHelpFunc(gloss.CharmHelp)
// rootCmd.SetUsageFunc(gloss.CharmUsage)
rootCmd.AddCommand(hugoCmd)
}

@ -0,0 +1,62 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"log"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"code.ndumas.com/ndumas/obsidian-pipeline"
)
var hugoBundleCmd = &cobra.Command{
Use: "bundle",
Short: "convert a set of Obsidian notes into a Hugo compatible directory structure",
Long: `generate hugo content from your vault`,
PreRunE: func(cmd *cobra.Command, args []string) error {
// here is where I validate arguments, open and parse config files, etc
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
source := viper.GetString("hugo.source")
target := viper.GetString("hugo.target")
err := obp.CopyPosts(source, target)
if err != nil {
return fmt.Errorf("error copying posts in %q: %w", source, err)
}
err = obp.Sanitize(source)
if err != nil {
return fmt.Errorf("error sanitizing posts in %q: %w", source, err)
}
err = obp.GatherMedia(source)
if err != nil {
return fmt.Errorf("error gathering media in %q: %w", source, err)
}
return nil
},
}
func init() {
hugoBundleCmd.Flags().StringP("source", "s", "", "path to vault directory containing hugo posts")
err := viper.BindPFlag("hugo.source", hugoBundleCmd.Flags().Lookup("source"))
if err != nil {
log.Panicln("error binding viper to source flag:", err)
}
hugoBundleCmd.Flags().StringP("target", "t", "", "hugo content/ directory")
err = viper.BindPFlag("hugo.target", hugoBundleCmd.Flags().Lookup("target"))
if err != nil {
log.Panicln("error binding viper to target flag:", err)
}
hugoBundleCmd.MarkFlagsRequiredTogether("source", "target")
hugoCmd.AddCommand(hugoBundleCmd)
}

@ -25,8 +25,8 @@ var validateCmd = &cobra.Command{
Long: `Validate YAML frontmatter with jsonschema
`,
RunE: func(cmd *cobra.Command, args []string) error {
schema := viper.GetString("schema")
target := viper.GetString("target")
schema := viper.GetString("validate.schema")
target := viper.GetString("validate.target")
if target == "" {
return fmt.Errorf("target flag must not be empty")
}
@ -34,7 +34,7 @@ var validateCmd = &cobra.Command{
err := fs.WalkDir(root, ".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
return fmt.Errorf("could not walk %q: %w", path, err)
}
if d.IsDir() {
@ -45,23 +45,25 @@ var validateCmd = &cobra.Command{
if err != nil {
return fmt.Errorf("error generating absolute path for %q", target)
}
target, err := os.Open(absPath)
file, err := os.Open(absPath)
if err != nil {
return fmt.Errorf("could not open target file: %w", err)
}
defer target.Close()
err = obp.Validate(schema, target)
defer file.Close()
err = obp.Validate(schema, file)
if err != nil {
details, ok := err.(*jsonschema.ValidationError)
if !ok {
return err
return fmt.Errorf("eror validating %q: %w", path, err)
}
obp.PrettyDetails(cmd.OutOrStdout(), viper.GetString("format"), details.DetailedOutput(), absPath)
}
return nil
})
return err
if err != nil {
return fmt.Errorf("walkfunc failed: %w", err)
}
return nil
},
}
@ -73,21 +75,21 @@ func init() {
validateCmd.Flags().StringP("target", "t", "", "directory containing validation targets")
validateCmd.MarkFlagsRequiredTogether("schema", "target")
validateCmd.PersistentFlags().StringVar(&format, "format", "markdown", "output format [markdown, json, csv]")
rootCmd.AddCommand(validateCmd)
err := viper.BindPFlag("schema", validateCmd.Flags().Lookup("schema"))
if err != nil {
log.Panicln("error binding viper to schema flag:", err)
}
err = viper.BindPFlag("target", validateCmd.Flags().Lookup("target"))
err = viper.BindPFlag("validate.target", validateCmd.Flags().Lookup("target"))
if err != nil {
log.Panicln("error binding viper to target flag:", err)
}
err = viper.BindPFlag("format", validateCmd.PersistentFlags().Lookup("format"))
err = viper.BindPFlag("validate.format", validateCmd.PersistentFlags().Lookup("format"))
if err != nil {
log.Panicln("error binding viper to format flag:", err)
}
rootCmd.AddCommand(validateCmd)
}

@ -0,0 +1,31 @@
/*
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package cmd
import (
"fmt"
"github.com/spf13/cobra"
"code.ndumas.com/ndumas/obsidian-pipeline"
)
// rootCmd represents the base command when called without any subcommands
var versionCmd = &cobra.Command{
Use: "version",
Short: "prints obp version info",
Long: `displays git tag and sha
`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s Build: %s\n", obp.Version, obp.Build)
},
}
func init() {
// Cobra also supports local flags, which will only run
// when this action is called directly.
// rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
rootCmd.AddCommand(versionCmd)
}

@ -3,7 +3,9 @@ Copyright © 2023 NAME HERE <EMAIL ADDRESS>
*/
package main
import "code.ndumas.com/ndumas/obsidian-pipeline/cmd/obp/cmd"
import (
"code.ndumas.com/ndumas/obsidian-pipeline/cmd/obp/cmd"
)
func main() {
cmd.Execute()

@ -6,33 +6,47 @@ require (
github.com/charmbracelet/lipgloss v0.7.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/spf13/viper v1.4.0
go.uber.org/zap v1.24.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/hashicorp/go-version v1.0.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/gox v1.0.1 // indirect
github.com/mitchellh/iochan v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/nicksnyder/go-i18n v1.10.1 // indirect
github.com/pelletier/go-toml v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518 // indirect
github.com/subosito/gotenv v1.4.2 // indirect
github.com/tebeka/go2xunit v1.4.10 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.1.12 // indirect
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.2.2 // indirect
)

107
go.sum

@ -38,10 +38,21 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/gometalinter v3.0.0+incompatible h1:e9Zfvfytsw/e6Kd/PYd75wggK+/kX5Xn8IYDUKyc5fU=
github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@ -51,10 +62,17 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -62,12 +80,21 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -103,6 +130,7 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@ -118,10 +146,18 @@ github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLe
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@ -130,10 +166,15 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@ -141,6 +182,7 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
@ -148,41 +190,76 @@ github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/gox v1.0.1 h1:x0jD3dcHk9a9xPSDN6YEL4xL6Qz0dvNYm8yZqui5chI=
github.com/mitchellh/gox v1.0.1/go.mod h1:ED6BioOGXMswlXa2zxfh/xdd5QhwYliBFn9V18Ap4z4=
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nicksnyder/go-i18n v1.10.1 h1:isfg77E/aCD7+0lD/D00ebR2MV5vgeQ276WYyDaCRQc=
github.com/nicksnyder/go-i18n v1.10.1/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0 h1:uIkTLo0AGRc8l7h5l9r+GcYi9qfVPt6lD4/bhmzfiKo=
github.com/santhosh-tekuri/jsonschema/v5 v5.3.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU=
github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518 h1:iD+PFTQwKEmbwSdwfvP5ld2WEI/g7qbdhmHJ2ASfYGs=
github.com/sqs/goreturns v0.0.0-20181028201513-538ac6014518/go.mod h1:CKI4AZ4XmGV240rTHfO0hfE83S6/a3/Q1siZJ/vXf7A=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -196,24 +273,35 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8=
github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/tebeka/go2xunit v1.4.10 h1:0UO+9YoLpXTZ0DL9XbTmIIibgmKBGiwroo8uhFMSyR0=
github.com/tebeka/go2xunit v1.4.10/go.mod h1:wmc9jKT7KlU4QLU6DNTaIXNnYNOjKKNlp6mjOS0UrqY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@ -254,14 +342,19 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -305,6 +398,9 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -354,6 +450,7 @@ golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -401,6 +498,8 @@ golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4f
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -469,6 +568,7 @@ google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
@ -493,12 +593,19 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 h1:CEBpW6C191eozfEuWdUmIAHn7lwlLxJ7HVdr2e2Tsrw=
gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1,92 @@
package obp
import (
"fmt"
"io"
"io/fs"
// "log"
"os"
"path/filepath"
"strings"
)
func copy(src, dst string) (int64, error) {
sourceFileStat, err := os.Stat(src)
if err != nil {
return 0, err
}
if !sourceFileStat.Mode().IsRegular() {
return 0, fmt.Errorf("%s is not a regular file", src)
}
source, err := os.Open(src)
if err != nil {
return 0, err
}
defer source.Close()
destination, err := os.Create(dst)
if err != nil {
return 0, err
}
defer destination.Close()
nBytes, err := io.Copy(destination, source)
return nBytes, err
}
func CopyPosts(src, dst string) error {
posts := make([]string, 0)
srcRoot := os.DirFS(src)
err := fs.WalkDir(srcRoot, ".", func(path string, d fs.DirEntry, err error) error {
// here's where I walk through the source directory and collect all the markdown notes
if err != nil {
return fmt.Errorf("could not walk %q: %w", path, err)
}
if d.IsDir() {
return nil
}
if strings.HasSuffix(path, ".md") {
posts = append(posts, filepath.Join(src, path))
}
return nil
})
if err != nil {
return fmt.Errorf("walkfunc failed: %w", err)
}
for _, post := range posts {
base := filepath.Base(post)
splitPostName := strings.Split(base, ".")
postName := strings.Join(splitPostName[:len(splitPostName)-1], ".")
postDir := filepath.Join(dst, postName)
err := os.MkdirAll(postDir, 0777)
if err != nil && !os.IsExist(err) {
return fmt.Errorf("error creating target directory %q: %w", dst, err)
}
_, err = copy(post, filepath.Join(postDir, "index.md"))
if err != nil {
return fmt.Errorf("error opening %q for copying: %w", post, err)
}
}
return nil
}
func Sanitize(src string) error {
return nil
}
func GatherMedia(src string) error {
return nil
}

@ -1,17 +1,27 @@
package obp
import (
"fmt"
"io/fs"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"
"go.uber.org/zap"
)
func NewPipeline(dev bool) *Pipeline {
var p Pipeline
var l *zap.Logger
l, _ = zap.NewProduction()
if dev {
l, _ = zap.NewDevelopment()
}
p.L = l
p.Attachments = make(map[string]string)
p.Posts = make([]string, 0)
@ -26,3 +36,169 @@ type Pipeline struct {
L *zap.Logger
BlogDir, AttachmentsDir string
}
func (p *Pipeline) Walk() error {
notesRoot := os.DirFS(p.Source)
blogRoot := os.DirFS(p.Target)
err := fs.WalkDir(notesRoot, ".", p.findAttachments)
if err != nil {
return fmt.Errorf("error scanning for attachments: %w", err)
}
err = fs.WalkDir(notesRoot, ".", p.findNotes)
if err != nil {
return fmt.Errorf("error scanning vault for posts: %w", err)
}
err = fs.WalkDir(blogRoot, ".", p.findPosts)
if err != nil {
return fmt.Errorf("error scanning blog for posts: %w", err)
}
return nil
}
func (p *Pipeline) findNotes(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindNotes").With(zap.String("path", path))
if strings.HasSuffix(path, ".md") && strings.Contains(path, p.BlogDir) {
walkLogger.Info("found blog post to publish, adding to index")
p.Notes = append(p.Notes, path)
}
return nil
}
func (p *Pipeline) findAttachments(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindAttachments").With(zap.String("path", path))
if strings.Contains(path, p.AttachmentsDir) {
walkLogger.Info("found attachment file, adding to index")
absPath, err := filepath.Abs(filepath.Join(p.Source, path))
if err != nil {
return fmt.Errorf("error generating absolute path for attachment %q: %w", path, err)
}
walkLogger.Info("adding Attachment",
zap.String("key", filepath.Base(absPath)),
zap.String("value", absPath),
)
p.Attachments[filepath.Base(absPath)] = absPath
}
return nil
}
func (p *Pipeline) findPosts(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
return nil
}
walkLogger := p.L.Named("FindPosts").With(zap.String("path", path))
if strings.HasSuffix(path, "index.md") {
walkLogger.Info("found index.md, adding to index")
p.Posts = append(p.Posts, path)
}
return nil
}
func (p *Pipeline) Move() error {
moveLogger := p.L.Named("Move")
moveLogger.Info("scanning posts", zap.Strings("posts", p.Posts))
for _, post := range p.Notes {
// log.Printf("scanning %q for attachment links", post)
linkedAttachments, err := extractAttachments(filepath.Join(p.Source, post))
if err != nil {
return fmt.Errorf("could not extract attachment links from %q: %w", post, err)
}
for _, attachment := range linkedAttachments {
att, ok := p.Attachments[attachment]
if !ok {
return fmt.Errorf("Attachment is linked by post %q but doesn't exist in attachments directory %q", post, p.AttachmentsDir)
}
err := moveAttachment(post, att, p.L.Named("moveAttachment"))
if err != nil {
return fmt.Errorf("error moving attachments: %w", err)
}
}
}
return nil
}
func moveAttachment(post, attachment string, l *zap.Logger) error {
l.Info("moving attachment",
zap.String("post", post),
zap.String("attachment", attachment),
)
return nil
}
func extractAttachments(post string) ([]string, error) {
pat := regexp.MustCompile(`\[\[Resources\/attachments\/(.*)?\]\]`)
attachments := make([]string, 0)
postBody, err := ioutil.ReadFile(post)
if err != nil {
return attachments, fmt.Errorf("error opening post to scan for attachment links: %w", err)
}
for _, att := range pat.FindAllSubmatch(postBody, -1) {
filename := string(att[1])
attachments = append(attachments, filename)
}
return attachments, nil
}
func (p *Pipeline) FindAttachments() error {
return nil
}
func (p *Pipeline) MoveAttachments(post string) error {
return nil
}
func (p *Pipeline) FindPosts() error {
return nil
}
func (p *Pipeline) SanitizePost(post string) error {
return nil
}
func (p *Pipeline) CopyPost(post string) error {
return nil
}

@ -1,15 +0,0 @@
package obp
func (p *Pipeline) FindPosts() error {
return nil
}
func (p *Pipeline) SanitizePost(post string) error {
return nil
}
func (p *Pipeline) CopyPost(post string) error {
return nil
}

@ -2,34 +2,41 @@ package obp
import (
"encoding/json"
"errors"
"fmt"
"io"
"github.com/santhosh-tekuri/jsonschema/v5"
// allow the jsonschema validator to auto-download http-hosted schemas.
_ "github.com/santhosh-tekuri/jsonschema/v5/httploader"
"gopkg.in/yaml.v3"
)
var ErrUnsupportedOutputFormat = errors.New("unspported output format")
// Validate accepts a Markdown file as input via the Reader
// and parses the frontmatter present, if any. It then
// applies the schema fetched from schemaURL against the
// decoded YAML.
func Validate(schemaURL string, r io.Reader) error {
var m interface{}
var frontmatter interface{}
dec := yaml.NewDecoder(r)
err := dec.Decode(&m)
err := dec.Decode(&frontmatter)
if err != nil {
return fmt.Errorf("error decoding YAML: %w", err)
}
compiler := jsonschema.NewCompiler()
schema, err := compiler.Compile(schemaURL)
if err != nil {
return fmt.Errorf("error compiling schema: %w", err)
}
if err := schema.Validate(m); err != nil {
return err
if err != nil {
return fmt.Errorf("frontmatter failed validation: %w", schema.Validate(frontmatter))
}
return nil
@ -39,6 +46,7 @@ func recurseDetails(detailed jsonschema.Detailed, acc map[string]jsonschema.Deta
if detailed.Error != "" {
acc[detailed.AbsoluteKeywordLocation] = detailed
}
for _, e := range detailed.Errors {
acc = recurseDetails(e, acc)
}
@ -49,29 +57,30 @@ func recurseDetails(detailed jsonschema.Detailed, acc map[string]jsonschema.Deta
// PrettyDetails takes error output from jsonschema.Validate
// and pretty-prints it to stdout.
//
// Supported formats are: JSON, Markdown
func PrettyDetails(w io.Writer, format string, details jsonschema.Detailed, filename string) error {
// Supported formats are: JSON, Markdown.
func PrettyDetails(writer io.Writer, format string, details jsonschema.Detailed, filename string) error {
// acc := make([]jsonschema.Detailed, 0)
acc := make(map[string]jsonschema.Detailed)
errors := recurseDetails(details, acc)
switch format {
case "json":
enc := json.NewEncoder(w)
enc := json.NewEncoder(writer)
err := enc.Encode(details)
if err != nil {
return fmt.Errorf("error writing JSON payload to provided writer: %w", err)
}
case "markdown":
fmt.Fprintf(w, "# Validation Errors for %q\n", filename)
fmt.Fprintf(w, "Validation Rule|Failing Property|Error\n")
fmt.Fprintf(w, "--|---|---\n")
fmt.Fprintf(writer, "# Validation Errors for %q\n", filename)
fmt.Fprintf(writer, "Validation Rule|Failing Property|Error\n")
fmt.Fprintf(writer, "--|---|---\n")
for _, e := range errors {
fmt.Fprintf(w, "%s|%s|%s\n", e.KeywordLocation, e.InstanceLocation, e.Error)
fmt.Fprintf(writer, "%s|%s|%s\n", e.KeywordLocation, e.InstanceLocation, e.Error)
}
default:
return fmt.Errorf("unknown format")
return ErrUnsupportedOutputFormat
}
return nil

@ -0,0 +1,77 @@
package obp_test
import (
"bytes"
"testing"
"code.ndumas.com/ndumas/obsidian-pipeline"
)
func Test_BasicValidation(t *testing.T) {
t.Parallel()
tt := []struct {
name string
b *bytes.Buffer
expected error
}{
{
name: "KeyMissing",
b: bytes.NewBufferString(`
---
boop: "bop"
---
# Markdown Content
`),
expected: nil,
},
{
name: "KeyTypeMismatch",
b: bytes.NewBufferString(`
---
title: 2
---
# Markdown Content
`),
expected: nil,
},
{
name: "GoodSchema",
b: bytes.NewBufferString(`
---
draft: false
title: "Mapping Aardwolf with Graphviz and Golang"
aliases: ["Mapping Aardwolf with Graphviz"]
series: ["mapping-aardwolf"]
date: "2023-04-06"
author: "Nick Dumas"
cover: ""
keywords: [""]
description: "Maxing out your CPU for fun and profit with dense graphs, or how I'm attempting to follow through on my plan to work on projects with more visual
outputs"
showFullContent: false
tags:
- graphviz
- graph
- aardwolf
- golang
---
## Textual Cartography
Aardwolf has a fairly active developer community, people who write and maintain plugins and try to map the game world and its contents.
`),
expected: nil,
},
}
for _, tc := range tt {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
err := obp.Validate("https://schemas.ndumas.com/obsidian/note.schema.json", tc.b)
if err == tc.expected {
t.Log("Expected Validate() to fail on input")
t.Fail()
}
})
}
}

@ -0,0 +1,5 @@
package obp
var (
Version, Build string
)
Loading…
Cancel
Save