~samwhited/checkmd

9dd91e3bdaead5f50cbce825a598a08fb644b308 — Sam Whited 1 year, 1 month ago a5c12d3
Initial commit with simple link parsing

Added an initial tool that verifies that links are valid URLs and is not
configurable.

See https://mellium.im/issue/41

Signed-off-by: Sam Whited <sam@samwhited.com>
8 files changed, 202 insertions(+), 0 deletions(-)

A .builds/ci.yml
A .builds/dco.yml
A .gitignore
A LICENSE
A README.md
A go.mod
A go.sum
A main.go
A .builds/ci.yml => .builds/ci.yml +30 -0
@@ 0,0 1,30 @@
image: freebsd/latest
packages:
  - go
sources:
  - https://git.sr.ht/~samwhited/checkmd
tasks:
  - setup: |
      go version
      go env

      go get -u golang.org/x/lint/golint
      go get -u github.com/securego/gosec/cmd/gosec
      go get -u git.sr.ht/~samwhited/checkdoc

      echo 'export PATH=$(go env GOPATH)/bin:$PATH' >> ~/.buildenv
  - lint: |
      cd checkmd/
      go vet ./...
      gofmt -s -l . && [ -z "$(gofmt -s -l .)" ]

      golint -set_exit_status ./...
      gosec ./...
      checkdoc ./...
  - validate: |
      cd checkmd/
      go mod tidy
      git diff --exit-code -- go.mod go.sum
  - run: |
      cd checkmd/
      go run . .

A .builds/dco.yml => .builds/dco.yml +27 -0
@@ 0,0 1,27 @@
image: alpine/edge
packages:
  - git
sources:
  - https://git.sr.ht/~samwhited/checkmd
tasks:
  - dco: |
      git version
      cd checkmd/
      function on_err {
        cat <<EOF
      Failed to sign the Developer Certificate of Origin (DCO)!
      Please read the file "DCO" and then, if you agree, sign each of your commits
      using:

          git commit -s

      Or quickly sign the previous commit with:

          git commit --amend -s --no-edit
      EOF
      }
      trap on_err ERR

      # Check that all commits that aren't in master are signed off by the same
      # committer (taken from the HEAD commit).
      [[ ! "$(git log --invert-grep --grep="Signed-off-by: $(git show -s --pretty="%cn <%ce>" HEAD)" origin/master..)" ]]

A .gitignore => .gitignore +1 -0
@@ 0,0 1,1 @@
checkmd

A LICENSE => LICENSE +23 -0
@@ 0,0 1,23 @@
Copyright © 2020 The Mellium Contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

A README.md => README.md +23 -0
@@ 0,0 1,23 @@
# checkmd

[![GoDoc](https://godoc.org/mellium.im/checkmd?status.svg)](https://godoc.org/mellium.im/checkmd)
[![License](https://img.shields.io/badge/license-FreeBSD-blue.svg)](https://opensource.org/licenses/BSD-2-Clause)

[![Buy Me A Coffee](https://www.buymeacoffee.com/assets/img/custom_images/purple_img.png)](https://www.buymeacoffee.com/samwhited)
[![Support Me](https://liberapay.com/assets/widgets/donate.svg)](https://liberapay.com/SamWhited/donate)

The `checkmd` tool parses Markdown files and reports common linter issues.
It is mainly used to validate the documentation for [`mellium.im/xmpp`].

## License

The package may be used under the terms of the BSD 2-Clause License a copy of
which may be found in the file "[LICENSE]".

Unless you explicitly state otherwise, any contribution submitted for inclusion
in the work by you shall be licensed as above, without any additional terms or
conditions.


[`mellium.im/xmpp`]: https://mellium.im/xmpp
[LICENSE]: https://git.sr.ht/~samwhited/checkmd/tree/master/LICENSE

A go.mod => go.mod +8 -0
@@ 0,0 1,8 @@
module mellium.im/checkmd

go 1.14

require (
	github.com/russross/blackfriday/v2 v2.0.1
	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
)

A go.sum => go.sum +5 -0
@@ 0,0 1,5 @@
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=

A main.go => main.go +85 -0
@@ 0,0 1,85 @@
// Copyright 2020 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

// The checkmd command parses Markdown documents and returns an error if any
// relative links or invalid URLs are found.
// Other tests may be added later.
package main

import (
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	"github.com/russross/blackfriday/v2"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "usage %s filenames\n", os.Args[0])
		os.Exit(1)
	}
	for _, root := range os.Args[1:] {
		if isMD(root) {
			err := checkFile(root)
			if err != nil {
				fmt.Fprintln(os.Stderr, err)
				os.Exit(1)
			}
			continue
		}

		// If the path doesn't appear to be a markdown file assume it's a directory
		// and try to walk it.
		err := filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error {
			if !isMD(fpath) {
				return nil
			}

			return checkFile(fpath)
		})
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
	}
}

func isMD(fpath string) bool {
	return strings.HasSuffix(fpath, ".md") || strings.HasSuffix(fpath, ".markdown")
}

func checkFile(fpath string) error {
	/* #nosec */
	md, err := ioutil.ReadFile(fpath)
	if err != nil {
		return err
	}
	node := blackfriday.New().Parse(md)
	var hasErrs int
	node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
		if node.Type != blackfriday.Link || !entering {
			return blackfriday.GoToNext
		}

		u, err := url.Parse(string(node.LinkData.Destination))
		if err != nil {
			hasErrs++
			fmt.Fprintf(os.Stderr, "link %q is not a valid URL: %v\n", node.LinkData.Destination, err)
			return blackfriday.GoToNext
		}
		if u.Scheme == "" {
			hasErrs++
			fmt.Fprintf(os.Stderr, "%s: link is missing the URL scheme: %s\n", fpath, u)
		}
		return blackfriday.GoToNext
	})
	if hasErrs > 0 {
		return fmt.Errorf("found %d errors", hasErrs)
	}
	return nil
}