123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218 |
- #!/usr/bin/env python3
- # A quick&dirty Markdown Validator
- # Copyright (C) 2018 Ingo Ruhnke <grumbel@gmail.com>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- from typing import List
- import os
- import sys
- import argparse
- from enum import Flag
- import requests
- # https://github.com/rtfd/CommonMark-py
- from CommonMark import Parser, dumpAST
- def url_valid(url: str) -> bool:
- try:
- request = requests.head(url, timeout=1.0)
- if request.ok:
- return True
- else:
- return False
- except Exception:
- return False
- class ValidatorFlags(Flag):
- # Validate everything
- ALL = ~0
- # Validate image
- IMAGE = (1 << 1)
- # Validate regular text links
- LINK = (1 << 2)
- # Validate anchor links (e.g. "#FOOBAR")
- ANCHOR = (1 << 3)
- # Validate external references (e.g. "http://...", "https://...")
- EXTERNAL = (1 << 4)
- # Validate local references
- LOCAL = (1 << 5)
- class Validator:
- def __init__(self, filename: str) -> None:
- self._filename = filename
- self._anchors = []
- def set_flags(self, flags):
- self._flags = flags
- def validate(self) -> None:
- with open(self._filename) as fin:
- parser = Parser()
- document = parser.parse(fin.read())
- for node, starttag in document.walker():
- self.preprocess(node, starttag)
- for node, starttag in document.walker():
- self.check(node, starttag)
- def get_pos(self, node) -> str:
- cur = node.parent
- while cur is not None:
- if cur.sourcepos is not None:
- return "{}:{}".format(self._filename, cur.sourcepos[0][0])
- else:
- cur = cur.parent
- return "{}".format(self._filename)
- def validate_link_destination(self, node) -> None:
- # print("link: {}".format(link))
- if node.destination.startswith("http://") or node.destination.startswith("https://"):
- if self._flags & ValidatorFlags.EXTERNAL:
- if not url_valid(node.destination):
- print("{}: error: {} failed to load".format(self.get_pos(node),
- node.destination))
- else:
- print("{}: OK".format(node.destination))
- elif node.destination.startswith("#"):
- if self._flags & ValidatorFlags.ANCHOR:
- self.validate_anchor(node, node.destination[1:])
- else:
- page, *rest = node.destination.split("#", 1)
- filename = page + ".md"
- if not os.path.exists(filename):
- print("{}: error: {} does not exist".format(self.get_pos(node),
- filename),
- file=sys.stderr)
- if rest and self._flags & ValidatorFlags.ANCHOR:
- # need to read in the other document to validate this
- # anchor properly
- # self.validate_anchor(node, rest[0])
- pass
- def validate_anchor(self, node, anchor: str) -> None:
- if anchor not in self._anchors:
- print("{}: error: {} anchor does not exist".format(self.get_pos(node),
- anchor),
- file=sys.stderr)
- def validate_image_destination(self, node) -> None:
- # print("image: {}".format(link))
- if node.destination.startswith("http://") or node.destination.startswith("https://"):
- if self._flags & ValidatorFlags.EXTERNAL:
- if not url_valid(node.destination):
- print("{}: error: {} failed to load".format(self.get_pos(node),
- node.destination))
- elif not os.path.exists(node.destination):
- print("{}: error: {} does not exist".format(self.get_pos(node),
- node.destination),
- file=sys.stderr)
- def get_text(self, node) -> str:
- result = ""
- for child, starttag in node.walker():
- if child.t == "text":
- result += child.literal
- return result
- def preprocess(self, node, starttag: bool):
- if starttag:
- if node.t == "heading":
- title = self.get_text(node)
- anchor = title.lower().replace(" ", "-")
- self._anchors.append(anchor)
- def check(self, node, starttag: bool):
- if starttag:
- if node.t == "link" and self._flags & ValidatorFlags.LINK:
- # children = list(node.walker())
- # print(children[1][0].literal)
- # print(node.destination)
- # print()
- self.validate_link_destination(node)
- elif node.t == "image" and self._flags & ValidatorFlags.IMAGE:
- # children = list(node.walker())
- # print(children[1][0].literal)
- # print(node.destination)
- # print()
- self.validate_image_destination(node)
- def parse_args(argv: List[str]) -> argparse.Namespace:
- # Parse Arguments
- parser = argparse.ArgumentParser(description="Flexlay - SuperTux Editor")
- parser.add_argument("FILE", action="store", type=str, nargs="+",
- help=".md file to verify")
- parser.add_argument("--validate", metavar="CATEGORY", type=str, default="all")
- parser.add_argument("--dump-ast", action='store_true', default=False)
- return parser.parse_args(argv[1:])
- def main(argv: List[str]):
- args = parse_args(argv)
- flags = ValidatorFlags(0)
- categories = args.validate.split(",")
- for category in categories:
- category = category.lower()
- if category == "all":
- flags |= ValidatorFlags.ALL
- elif category == "external":
- flags |= ValidatorFlags.EXTERNAL
- elif category == "local":
- flags |= ValidatorFlags.LOCAL
- elif category == "link":
- flags |= ValidatorFlags.LINK
- elif category == "anchor":
- flags |= ValidatorFlags.ANCHOR
- elif category == "image":
- flags |= ValidatorFlags.IMAGE
- else:
- raise Exception("unknown category '{}'".format(category))
- for filename in args.FILE:
- if args.dump_ast:
- with open(filename) as fin:
- parser = Parser()
- document = parser.parse(fin.read())
- dumpAST(document)
- else:
- validator = Validator(filename)
- validator.set_flags(flags)
- validator.validate()
- if __name__ == "__main__":
- main(sys.argv)
- # EOF #
|