Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more lint command #71

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 48 additions & 0 deletions cmake/roslint-extras.cmake.em
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,54 @@ function(roslint_python)
roslint_custom("${ROSLINT_PYTHON_CMD}" "${ROSLINT_PYTHON_OPTS}" ${ARGN})
endfunction()

# Run yamllint on a list of file names.
#
function(roslint_yaml)
if ("${ARGN}" STREQUAL "")
file(GLOB_RECURSE ARGN *.yaml)
endif()
if (NOT DEFINED ROSLINT_YAML_CMD)
set(ROSLINT_YAML_CMD /usr/bin/yamllint)
endif()
if ("${ROSLINT_YAML_OPTS}" STREQUAL "")
set(ROSLINT_YAML_OPTS "{extends: default, rules: {document-start: disable}}")
endif()
roslint_custom("${ROSLINT_YAML_CMD}" "-d ${ROSLINT_YAML_OPTS}" "--strict" ${ARGN})
endfunction()

# Run xmllint on a list of file names.
#
function(roslint_xml)
if ("${ARGN}" STREQUAL "")
file(GLOB_RECURSE ARGN *.xml *.launch)
endif()
if (NOT DEFINED ROSLINT_XML_CMD)
set(ROSLINT_XML_CMD ${ROSLINT_SCRIPTS_DIR}/xmllint)
endif()
roslint_custom("${ROSLINT_XML_CMD}" "${ROSLINT_XML_OPTS}" ${ARGN})
endfunction()

# Run markdown lint
#
function(roslint_markdown)
if ("${ARGN}" STREQUAL "")
file(GLOB_RECURSE ARGN *.md)
endif()
if (NOT DEFINED ROSLINT_MARKDOWN_CMD)
set(ROSLINT_MARKDOWN_CMD ${ROSLINT_SCRIPTS_DIR}/markdownlint)
endif()
roslint_custom("${ROSLINT_MARKDOWN_CMD}" "${ROSLINT_MARKDOWN_OPTS}" ${ARGN})
endfunction()

# Run catkin_lint
#
function(roslint_catkin)
if (NOT DEFINED ROSLINT_CATKIN_CMD)
set(ROSLINT_CATKIN_CMD ${ROSLINT_SCRIPTS_DIR}/catkin_lint)
endif()
roslint_custom("${ROSLINT_CATKIN_CMD}" "${ROSLINT_CATKIN_OPTS}" ${PROJECT_SOURCE_DIR})
endfunction()

# Run roslint for this package as a test.
function(roslint_add_test)
catkin_run_tests_target("roslint" "package" "roslint-${PROJECT_NAME}.xml"
Expand Down
3 changes: 3 additions & 0 deletions package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@
<run_depend>python-pylint</run_depend>
<run_depend>python-cpplint</run_depend>
-->
<run_depend>python-catkin-lint</run_depend>
<run_depend>python-lxml</run_depend>
<run_depend>yamllint</run_depend>

</package>
10 changes: 10 additions & 0 deletions scripts/catkin_lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env python

import sys
from catkin_lint.main import main

# Treat warnings as errors and output result as text with explanations.
# The number is 1 since 0 is the name of the program.
sys.argv[1:1] = ["-W2", "--strict", "--explain"]
print(sys.argv)
main()
77 changes: 77 additions & 0 deletions scripts/markdownlint
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/usr/bin/env python

import argparse
import sys
try:
from roslint import pymarkdownlint_lint as lint
from roslint import pymarkdownlint_rules as rules
from collections import OrderedDict
except ImportError:
# Allows the target to work with an un-sourced workspace.
import os.path
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "src"))
from roslint import pymarkdownlint_lint as lint
from roslint import pymarkdownlint_rules as rules
from collections import OrderedDict

'''
The MIT License (MIT)

Copyright (c) 2015 Joris Roovers

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
'''


# LintConfig copied from https://raw.githubusercontent.com/jorisroovers/pymarkdownlint/master/pymarkdownlint/config.py
class LintConfig(object):
""" Class representing markdownlint configuration """
default_rule_classes = [rules.MaxLineLengthRule, rules.TrailingWhiteSpace, rules.HardTab]

def __init__(self):
# Use an ordered dict so that the order in which rules are applied is always the same
self._rules = OrderedDict([(rule_cls.id, rule_cls()) for rule_cls in self.default_rule_classes])

@property
def rules(self):
return self._rules.values()

def get_rule_by_name_or_id(self, rule_id_or_name):
# try finding rule by id
rule = self._rules.get(rule_id_or_name)
# if not found, try finding rule by name
if not rule:
rule = next((rule for rule in self.rules if rule.name == rule_id_or_name), None)
return rule

# Prepend max line-length so that user can overide it. The number is 1
# since 0 is the name of the program.
sys.argv.insert(1, "--max-line-length=120")

parser = argparse.ArgumentParser(description='markdownlint')
parser.add_argument('--max-line-length', type=int, help='set max line length')
parser.add_argument('files', type=str, nargs='+', help='list of file names')
args = parser.parse_args()

# force override line-length value
lint_config = LintConfig()
lint_config.get_rule_by_name_or_id('max-line-length').options['line-length'].value = args.max_line_length
linter = lint.MarkdownLinter(lint_config)
error_count = linter.lint_files(args.files)
exit(error_count)
81 changes: 81 additions & 0 deletions scripts/xmllint
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
#!/usr/bin/env python

import argparse
import sys


def raw_xml(filename):
xml_string = ''
with open(filename, 'r') as file:
for line in file:
xml_string += line
return(xml_string)

# https://stackoverflow.com/questions/749796/pretty-printing-xml-in-python
import lxml.etree as etree


def pretty_xml(filename):
xml_string = ''
with open(filename, 'r') as file:
for line in file:
if line == "\n": # preserve blank line
line = "<nr/>\n"
xml_string += line

try:
parser = etree.XMLParser(remove_blank_text=True, compact=False)
x = etree.XML(xml_string, parser)
except Exception as e:
print(e)
print("Failed to parse XML file {}".format(filename))
return None

# check white space within element and atributes
for element in x.iter():
if element.text:
element.text = element.text.strip()
for attr in element.keys():
element.attrib[attr] = element.attrib[attr].strip()

pretty_xml_as_string = etree.tostring(x, pretty_print=True, xml_declaration=True, with_tail=False)
pretty_xml_as_string = pretty_xml_as_string.replace(' <nr/>', '')
return(pretty_xml_as_string)

# https://docs.python.org/2/library/difflib.html
import difflib


def diff(s1, s2):
s1 = s1.splitlines(1)[1:]
s2 = s2.splitlines(1)[1:]
diff = difflib.ndiff(s1, s2)
if list(s1) != list(s2):
return ''.join(diff)
else:
return None

parser = argparse.ArgumentParser(description='markdownlint')
parser.add_argument('--fix', action='store_true', help='fix input xml file')
parser.add_argument('files', type=str, nargs='+', help='list of file names')
args = parser.parse_args()

error = False
for filename in args.files:
xml_as_string = raw_xml(filename)
pretty_xml_as_string = pretty_xml(filename)
if pretty_xml_as_string is None:
error = True
else:
if args.fix:
with open(filename, 'w') as file:
file.write(pretty_xml_as_string)
else:
diff_string = diff(xml_as_string, pretty_xml_as_string)
if diff_string: # diff
print("Lint error on {} file".format(filename))
print(diff_string)
error = True

if error:
exit(-1)
5 changes: 5 additions & 0 deletions src/roslint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,10 @@ Acquiring these scripts:

wget http://google-styleguide.googlecode.com/svn/trunk/cpplint/cpplint.py -O cpplint.py
wget https://raw.github.com/jcrocholl/pep8/master/pep8.py -O pep8.py
wget https://github.com/jorisroovers/pymarkdownlint/raw/master/pymarkdownlint/lint.py -O pymarkdownlint_lint.py
wget https://github.com/jorisroovers/pymarkdownlint/raw/master/pymarkdownlint/rules.py -O pymarkdownlint_rules.py
wget https://github.com/jorisroovers/pymarkdownlint/raw/master/pymarkdownlint/options.py -O pymarkdownlint_options.py
sed -i 's/from pymarkdownlint import rules/import pymarkdownlint_rules as rules/' pymarkdownlint_lint.py
sed -i 's/from pymarkdownlint.options import IntOption/from pymarkdownlint_options import IntOption/' pymarkdownlint_rules.py

TODO: Have CMake download them at package build time?
54 changes: 54 additions & 0 deletions src/roslint/pymarkdownlint_lint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import print_function
import pymarkdownlint_rules as rules

class MarkdownLinter(object):
def __init__(self, config):
self.config = config

@property
def line_rules(self):
return [rule for rule in self.config.rules if isinstance(rule, rules.LineRule)]

def _apply_line_rules(self, markdown_string):
""" Iterates over the lines in a given markdown string and applies all the enabled line rules to each line """
all_violations = []
lines = markdown_string.split("\n")
line_rules = self.line_rules
line_nr = 1
ignoring = False
for line in lines:
if ignoring:
if line.strip() == '<!-- markdownlint:enable -->':
ignoring = False
else:
if line.strip() == '<!-- markdownlint:disable -->':
ignoring = True
continue

for rule in line_rules:
violation = rule.validate(line)
if violation:
violation.line_nr = line_nr
all_violations.append(violation)
line_nr += 1
return all_violations

def lint(self, markdown_string):
all_violations = []
all_violations.extend(self._apply_line_rules(markdown_string))
return all_violations

def lint_files(self, files):
""" Lints a list of files.
:param files: list of files to lint
:return: a list of violations found in the files
"""
all_violations = []
for filename in files:
with open(filename, 'r') as f:
content = f.read()
violations = self.lint(content)
all_violations.extend(violations)
for e in violations:
print("{0}:{1}: {2} {3}".format(filename, e.line_nr, e.rule_id, e.message))
return len(all_violations)
39 changes: 39 additions & 0 deletions src/roslint/pymarkdownlint_options.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from abc import abstractmethod


class RuleOptionError(Exception):
pass


class RuleOption(object):
def __init__(self, name, value, description):
self.name = name
self.value = value
self.description = description

@abstractmethod
def set(self, value):
""" Validates and sets the option's value """
pass


class IntOption(RuleOption):
def __init__(self, name, value, description, allow_negative=False):
super(IntOption, self).__init__(name, value, description)
self.allow_negative = allow_negative

def raise_exception(self, value):
if self.allow_negative:
error_msg = "Option '{0}' must be an integer (current value: {1})".format(self.name, value)
else:
error_msg = "Option '{0}' must be a positive integer (current value: {1})".format(self.name, value)
raise RuleOptionError(error_msg)

def set(self, value):
try:
self.value = int(value)
except ValueError:
self.raise_exception(value)

if not self.allow_negative and self.value < 0:
self.raise_exception(value)