lib/cc/engine/markdownlint.rb
require "digest/md5"
require "json"
require "posix/spawn"
module CC
module Engine
class Markdownlint
CONFIG_FILE = "./.mdlrc".freeze
EXTENSIONS = %w[.markdown .md].freeze
UnexpectedOutputFormat = Class.new(StandardError)
LINES_TO_SKIP = [
"\n".freeze,
"A detailed description of the rules is available at https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md\n".freeze,
].freeze
def initialize(root, engine_config, io, err_io)
@root = root
@engine_config = engine_config
@io = io
@err_io = err_io
@contents = {}
end
def run
return if include_paths.length == 0
child = POSIX::Spawn::Child.new("mdl", *mdl_options, *include_paths)
out = child.out.force_encoding("UTF-8")
err = child.err.force_encoding("UTF-8")
if err.chars.any?
err_io.puts(err)
end
out.each_line do |line|
if (details = issue(line))
io.print JSON.dump(details)
io.print "\0"
end
end
end
private
attr_reader :root, :engine_config, :err_io, :io, :contents
def include_paths
return [root] unless engine_config.has_key?("include_paths")
@include_paths ||= engine_config["include_paths"].select do |path|
EXTENSIONS.include?(File.extname(path)) || path.end_with?("/")
end
end
def mdl_options
options = ["--no-warnings"]
options << "--config" << CONFIG_FILE if File.exist?(CONFIG_FILE)
options
end
ISSUE_PATTERN = /(?<path>.*):(?<line_number>\d+): (?<code>MD\d+) (?<description>.*)/
def issue(line)
return if LINES_TO_SKIP.include?(line)
match_data = line.match(ISSUE_PATTERN) or raise UnexpectedOutputFormat, line
line_number = match_data[:line_number].to_i
path = match_data[:path]
relative_path = File.absolute_path(path).sub(root + "/", "")
check_name = match_data[:code]
body = content(check_name)
issue = {
categories: ["Style"],
check_name: check_name,
description: match_data[:description],
fingerprint: fingerprint(check_name, path, line_number),
location: {
lines: {
begin: line_number,
end: line_number,
},
path: relative_path,
},
type: "issue",
remediation_points: 50_000,
severity: "info",
}
issue[:content] = { body: body } if body
issue
end
def content(code)
contents.fetch(code) do
filename = "../../../contents/#{code}.md"
path = File.expand_path(filename, File.dirname(__FILE__))
if File.exist?(path)
content = File.read(path)
contents[code] = content
end
end
end
def fingerprint(check_name, path, line_number)
md5 = Digest::MD5.new
md5 << check_name
md5 << path
md5 << read_line(path, line_number).gsub(/\s/, "")
md5.hexdigest
end
def read_line(path, line_number_to_read)
File.open(path) do |file|
file.each_line.with_index do |line, current_line_number|
return line if current_line_number == line_number_to_read - 1
end
end
""
end
end
end
end