Conversion to Python is complete. Goodbye, Hy, you were a brilliant and
functional step toward helping me organize my thoughts, but it's time to make this into something other Pythonistas can read. Sad, but true.
This commit is contained in:
parent
56037fe109
commit
f2873bc07c
|
@ -8,7 +8,7 @@ condition = output
|
||||||
|
|
||||||
[jshint]
|
[jshint]
|
||||||
output = Running Jshint...
|
output = Running Jshint...
|
||||||
command = jshint -c %(repdir)s/.git-lint/jshint.rc
|
command = jshint -c %(repodir)s/.git-lint/jshint.rc
|
||||||
match = .js
|
match = .js
|
||||||
print = False
|
print = False
|
||||||
condition = error
|
condition = error
|
||||||
|
@ -22,7 +22,7 @@ condition = error
|
||||||
|
|
||||||
[jscs]
|
[jscs]
|
||||||
output = Running JSCS...
|
output = Running JSCS...
|
||||||
command = jscs -c %(repdir)s/.git-lint/jscs.rc
|
command = jscs -c %(repodir)s/.git-lint/jscs.rc
|
||||||
match = .js
|
match = .js
|
||||||
print = False
|
print = False
|
||||||
condition = error
|
condition = error
|
||||||
|
|
|
@ -6,7 +6,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from git_lint_options import hyopt
|
from git_lint_options import RationalOptions
|
||||||
from git_lint_config import get_config
|
from git_lint_config import get_config
|
||||||
|
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
@ -15,21 +15,45 @@ VERSION = '0.0.2'
|
||||||
|
|
||||||
|
|
||||||
optlist = [
|
optlist = [
|
||||||
('o', 'only', True, _('A comma-separated list of only those linters to run'), ['exclude']),
|
('o', 'only', True,
|
||||||
('x', 'exclude', True, _('A comma-separated list of linters to skip'), []),
|
_('A comma-separated list of only those linters to run'), ['exclude']),
|
||||||
('l', 'linters', False, _('Show the list of configured linters')),
|
('x', 'exclude', True,
|
||||||
('b', 'base', False, _('Check all changed files in the repository, not just those in the current directory.'), []),
|
_('A comma-separated list of linters to skip'), []),
|
||||||
('a', 'all', False, _('Scan all files in the repository, not just those that have changed.')),
|
('l', 'linters', False,
|
||||||
('e', 'every', False, _('Short for -b -a: scan everything')], ['w', 'workspace', False, _('Scan the workspace'), ['staging']),
|
_('Show the list of configured linters'), []),
|
||||||
('s', 'staging', False, _('Scan the staging area (useful for pre-commit).'), []),
|
('b', 'base', False,
|
||||||
('g', 'changes', False, _(u"Report lint failures only for diff'd sections"), ['complete']),
|
_('Check all changed files in the repository, not just those in the current directory.'), []),
|
||||||
('p', 'complete', False, _('Report lint failures for all files'), []], ['c', 'config', True, _('Path to config file'), []),
|
('a', 'all', False,
|
||||||
('h', 'help', False, _('This help message'), []], ['v', 'version', False, _('Version information'), [])]
|
_('Scan all files in the repository, not just those that have changed.'), []),
|
||||||
|
('e', 'every', False,
|
||||||
|
_('Short for -b -a: scan everything'), []),
|
||||||
|
('w', 'workspace', False,
|
||||||
|
_('Scan the workspace'), ['staging']),
|
||||||
|
('s', 'staging', False,
|
||||||
|
_('Scan the staging area (useful for pre-commit).'), []),
|
||||||
|
('g', 'changes', False,
|
||||||
|
_("Report lint failures only for diff'd sections"), ['complete']),
|
||||||
|
('p', 'complete', False,
|
||||||
|
_('Report lint failures for all files'), []),
|
||||||
|
('c', 'config', True,
|
||||||
|
_('Path to config file'), []),
|
||||||
|
('h', 'help', False,
|
||||||
|
_('This help message'), []),
|
||||||
|
('v', 'version', False,
|
||||||
|
_('Version information'), [])]
|
||||||
|
|
||||||
|
|
||||||
|
# ___ _ _
|
||||||
|
# / __(_) |_
|
||||||
|
# | (_ | | _|
|
||||||
|
# \___|_|\__|
|
||||||
|
#
|
||||||
|
|
||||||
def get_git_response_raw(cmd):
|
def get_git_response_raw(cmd):
|
||||||
fullcmd = ([u'git'] + cmd)
|
fullcmd = (['git'] + cmd)
|
||||||
process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
process = subprocess.Popen(fullcmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
(out, err) = process.communicate()
|
(out, err) = process.communicate()
|
||||||
return (out, err, process.returncode)
|
return (out, err, process.returncode)
|
||||||
|
|
||||||
|
@ -46,23 +70,30 @@ def split_git_response(cmd):
|
||||||
|
|
||||||
def run_git_command(cmd):
|
def run_git_command(cmd):
|
||||||
fullcmd = (['git'] + cmd)
|
fullcmd = (['git'] + cmd)
|
||||||
return subprocess.call(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
return subprocess.call(fullcmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
|
||||||
def get_shell_response(fullcmd):
|
def get_shell_response(fullcmd):
|
||||||
process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
process = subprocess.Popen(fullcmd,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
shell=True)
|
||||||
(out, err) = process.communicate()
|
(out, err) = process.communicate()
|
||||||
return (out, err, process.returncode)
|
return (out, err, process.returncode)
|
||||||
|
|
||||||
|
|
||||||
def get_git_base():
|
def get_git_base():
|
||||||
(out, error, returncode) = get_git_response_raw(['rev-parse', '--show-toplevel'])
|
(out, error, returncode) = get_git_response_raw(
|
||||||
|
['rev-parse', '--show-toplevel'])
|
||||||
return returncode == 0 and out.rstrip() or None
|
return returncode == 0 and out.rstrip() or None
|
||||||
|
|
||||||
|
|
||||||
def get_git_head():
|
def get_git_head():
|
||||||
empty_repository_hash = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
|
empty_repository_hash = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
|
||||||
(out, err, returncode) = get_git_response_raw(['rev-parse', '--verify HEAD'])
|
(out, err, returncode) = get_git_response_raw(
|
||||||
|
['rev-parse', '--verify HEAD'])
|
||||||
return (err and empty_repository_hash or 'HEAD')
|
return (err and empty_repository_hash or 'HEAD')
|
||||||
|
|
||||||
|
|
||||||
|
@ -70,49 +101,58 @@ git_base = get_git_base()
|
||||||
git_head = get_git_head()
|
git_head = get_git_head()
|
||||||
|
|
||||||
|
|
||||||
def encode_shell_messages(prefix, messages):
|
# _ _ _ _ _ _ _ _
|
||||||
return ['{}{}'.format(prefix, line.decode('utf-8')) for line in messages.splitlines()]
|
# | | | | |_(_) (_) |_(_)___ ___
|
||||||
|
# | |_| | _| | | | _| / -_|_-<
|
||||||
|
# \___/ \__|_|_|_|\__|_\___/__/
|
||||||
|
#
|
||||||
|
|
||||||
|
def base_file_cleaner(files):
|
||||||
def run_external_checker(path, config):
|
return [file.replace(git_base, '', 1) for file in files]
|
||||||
cmd = config[u'command'].format((command + ' "{}"'), path)
|
|
||||||
(out, err, returncode) = get_shell_response(cmd)
|
|
||||||
if ((out and (check.get('error_condition', 'error') == 'output')) or err or (not (returncode == 0))):
|
|
||||||
prefix = check['print_filename'] and '\t{}:'.format(filename) or '\t'
|
|
||||||
output = encode_shell_messages(prefix, out) + (err and encode_shell_messages(prefix, err) or [])
|
|
||||||
return [(returncode or 1), output]
|
|
||||||
return [0, []]
|
|
||||||
|
|
||||||
|
|
||||||
def make_match_filter_matcher(extensions):
|
def make_match_filter_matcher(extensions):
|
||||||
trimmed = reduce(operator.add, [s.strip for s in [ex.split(',') for ex in extension-s]])
|
trimmed = reduce(operator.add, [s.strip for s in
|
||||||
|
[ex.split(',') for ex in extension-s]])
|
||||||
cleaned = [re.sub(r'^\.', s.strip(), '') for s in trimmed]
|
cleaned = [re.sub(r'^\.', s.strip(), '') for s in trimmed]
|
||||||
return re.compile(r'\.' + '|'.join(cleaned) + r'$')
|
return re.compile(r'\.' + '|'.join(cleaned) + r'$')
|
||||||
|
|
||||||
|
|
||||||
def make_match_filter(config):
|
def make_match_filter(config):
|
||||||
matcher = make_match_filter_matcher([v.get('match', '') for v in config.itervalues()])
|
matcher = make_match_filter_matcher([v.get('match', '')
|
||||||
|
for v in config.itervalues()])
|
||||||
|
|
||||||
def match_filter(path):
|
def match_filter(path):
|
||||||
return matcher.search(path)
|
return matcher.search(path)
|
||||||
|
|
||||||
return match_filter
|
return match_filter
|
||||||
|
|
||||||
|
|
||||||
|
# ___ _ _ _ _ _
|
||||||
|
# / __| |_ ___ __| |__ | (_)_ _| |_ ___ _ _ ___
|
||||||
|
# | (__| ' \/ -_) _| / / | | | ' \ _/ -_) '_(_-<
|
||||||
|
# \___|_||_\___\__|_\_\ |_|_|_||_\__\___|_| /__/
|
||||||
|
#
|
||||||
|
|
||||||
def executable_exists(script, label):
|
def executable_exists(script, label):
|
||||||
if not len(script):
|
if not len(script):
|
||||||
sys.exit(_('Syntax error in command configuration for {} ').format(label))
|
sys.exit(
|
||||||
|
_('Syntax error in command configuration for {} ').format(label))
|
||||||
|
|
||||||
scriptname = script.split(' ').pop(0)
|
scriptname = script.split(' ').pop(0)
|
||||||
if not len(scriptname):
|
if not len(scriptname):
|
||||||
sys.exit(_('Syntax error in command configuration for {} ').format(label))
|
sys.exit(
|
||||||
|
_('Syntax error in command configuration for {} ').format(label))
|
||||||
|
|
||||||
def isexecutable(path):
|
def is_executable(path):
|
||||||
return os.path.exists(path) and os.access(path, os.X_OK)
|
return os.path.exists(path) and os.access(path, os.X_OK)
|
||||||
|
|
||||||
if scriptname.startswith('/'):
|
if scriptname.startswith('/'):
|
||||||
return isexecutable(scriptname) and scriptname or None
|
return is_executable(scriptname) and scriptname or None
|
||||||
|
|
||||||
possibles = [path for path in
|
possibles = [path for path in
|
||||||
[os.path.join(path, scriptname) for path in os.environ.get('PATH').split(':')]
|
[os.path.join(path, scriptname)
|
||||||
|
for path in os.environ.get('PATH').split(':')]
|
||||||
if is_executable(path)]
|
if is_executable(path)]
|
||||||
return len(possibles) and possibles.pop(0) or None
|
return len(possibles) and possibles.pop(0) or None
|
||||||
|
|
||||||
|
@ -132,91 +172,141 @@ def print_linters(config):
|
||||||
print('{:<14} {}'.format(key, _('(WARNING: executable not found)')))
|
print('{:<14} {}'.format(key, _('(WARNING: executable not found)')))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# ___ _ _ _ _ __ __ _ _
|
||||||
|
# / __|___| |_ | (_)__| |_ ___ / _| / _(_) |___ ___
|
||||||
|
# | (_ / -_) _| | | (_-< _| / _ \ _| | _| | / -_|_-<
|
||||||
|
# \___\___|\__| |_|_/__/\__| \___/_| |_| |_|_\___/__/
|
||||||
|
#
|
||||||
|
|
||||||
|
def get_filelist(options):
|
||||||
|
""" Returns the list of files against which we'll run the linters. """
|
||||||
|
|
||||||
def base_file_filter(files):
|
def base_file_filter(files):
|
||||||
|
""" Return the full path for all files """
|
||||||
return [os.path.join(git_base, file) for file in files]
|
return [os.path.join(git_base, file) for file in files]
|
||||||
|
|
||||||
|
|
||||||
def cwd_file_filter(files):
|
def cwd_file_filter(files):
|
||||||
|
""" Return the full path for only those files in the cwd and down """
|
||||||
gitcwd = os.path.join(os.path.relpath(os.getcwd(), git_base), '')
|
gitcwd = os.path.join(os.path.relpath(os.getcwd(), git_base), '')
|
||||||
return base_file_filter([file for file in files
|
return base_file_filter([file for file in files
|
||||||
if file.startswith(gitcwd)])
|
if file.startswith(gitcwd)])
|
||||||
|
|
||||||
|
|
||||||
def base_file_cleaner(files):
|
|
||||||
return [file.replace(git_base, '', 1) for file in files]
|
|
||||||
|
|
||||||
|
|
||||||
MERGE_CONFLICT_PAIRS = set(['DD', 'DU', 'AU', 'AA', 'UD', 'UA', 'UU'])
|
|
||||||
def check_for_conflicts(filesets):
|
def check_for_conflicts(filesets):
|
||||||
|
""" Scan list of porcelain files for merge conflic state. """
|
||||||
|
MERGE_CONFLICT_PAIRS = set(['DD', 'DU', 'AU', 'AA', 'UD', 'UA', 'UU'])
|
||||||
status_pairs = set(['' + f[0] + f[1] for f in files])
|
status_pairs = set(['' + f[0] + f[1] for f in files])
|
||||||
if len(status_pairs & MERGE_CONFLICT_PAIRS):
|
if len(status_pairs & MERGE_CONFLICT_PAIRS):
|
||||||
sys.exit(_('Current repository contains merge conflicts. Linters will not be run.'))
|
sys.exit(
|
||||||
|
_('Current repository contains merge conflicts. Linters will not be run.'))
|
||||||
return filesets
|
return filesets
|
||||||
|
|
||||||
|
|
||||||
def remove_submodules(files):
|
def remove_submodules(files):
|
||||||
|
""" Remove all submodules from the list of files git-lint cares about. """
|
||||||
|
|
||||||
fixer_re = re.compile('^(\\.\\.\\/)+')
|
fixer_re = re.compile('^(\\.\\.\\/)+')
|
||||||
submodules = split_git_response(['submodule', 'status'])
|
submodules = split_git_response(['submodule', 'status'])
|
||||||
submodule_names = [fixer_re.sub('', submodule.split(' ')[2]) for submodule in submodules]
|
submodule_names = [fixer_re.sub('', submodule.split(' ')[2])
|
||||||
|
for submodule in submodules]
|
||||||
return [file for file in files if (file not in submodule_names)]
|
return [file for file in files if (file not in submodule_names)]
|
||||||
|
|
||||||
|
|
||||||
def get_porcelain_status():
|
def get_porcelain_status():
|
||||||
cmd = [u'status', u'-z', u'--porcelain', u'--untracked-files=all', u'--ignore-submodules=all']
|
""" Return the status of all files in the system. """
|
||||||
|
cmd = ['status', '-z', '--porcelain',
|
||||||
|
'--untracked-files=all', '--ignore-submodules=all']
|
||||||
stream = [entry for entry in get_git_response(cmd).split(u'\x00')
|
stream = [entry for entry in get_git_response(cmd).split(u'\x00')
|
||||||
if len(entry) > 0]
|
if len(entry) > 0]
|
||||||
acc = []
|
|
||||||
|
|
||||||
while len(stream) > 0:
|
def parse_stream(acc, stream):
|
||||||
|
"""Parse the list of files. T
|
||||||
|
|
||||||
|
The list is null-terminated, but is not columnar. If
|
||||||
|
there's an 'R' in the index state, it means the file was
|
||||||
|
renamed and the old name is added as a column, so it's a
|
||||||
|
special case as we accumulate the list of files.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if len(stream) == 0:
|
||||||
|
return acc
|
||||||
entry = stream.pop(0)
|
entry = stream.pop(0)
|
||||||
(index, workspace, filename) = (entry[0], entry[1], entry[3:])
|
(index, workspace, filename) = (entry[0], entry[1], entry[3:])
|
||||||
if index == 'R':
|
if index == 'R':
|
||||||
stream.pop(0)
|
stream.pop(0)
|
||||||
acc = acc + [(index, workspace, filename)]
|
return parse_stream(acc + [(index, workspace, filename)], stream)
|
||||||
return acc
|
|
||||||
|
return check_for_conflicts(parse_stream([], stream))
|
||||||
|
|
||||||
|
|
||||||
def staging_list():
|
def staging_list():
|
||||||
|
""" Return the list of files added or modified to the stage """
|
||||||
|
|
||||||
return [filename for (index, workspace, filename) in get_porcelain_status()
|
return [filename for (index, workspace, filename) in get_porcelain_status()
|
||||||
if index in ['A', 'M']]
|
if index in ['A', 'M']]
|
||||||
|
|
||||||
|
|
||||||
def working_list():
|
def working_list():
|
||||||
|
""" Return the list of files that have been modified in the workspace.
|
||||||
|
|
||||||
|
Includes the '?' to include files that git is not currently tracking.
|
||||||
|
"""
|
||||||
return [filename for (index, workspace, filename) in get_porcelain_status()
|
return [filename for (index, workspace, filename) in get_porcelain_status()
|
||||||
if workspace in ['A', 'M', '?']]
|
if workspace in ['A', 'M', '?']]
|
||||||
|
|
||||||
|
|
||||||
def all_list():
|
def all_list():
|
||||||
|
""" Return all the files git is currently tracking for this repository. """
|
||||||
cmd = ['ls-tree', '--name-only', '--full-tree', '-r', '-z', git_head]
|
cmd = ['ls-tree', '--name-only', '--full-tree', '-r', '-z', git_head]
|
||||||
return [file for file in get_git_response(cmd).split(u'\x00')
|
return [file for file in get_git_response(cmd).split(u'\x00')
|
||||||
if len(file) > 0]
|
if len(file) > 0]
|
||||||
|
|
||||||
|
|
||||||
def get_filelist(options):
|
|
||||||
keys = options.keys()
|
keys = options.keys()
|
||||||
|
|
||||||
working_directory_trans = cwd_file_filter
|
working_directory_trans = cwd_file_filter
|
||||||
if len(set(keys) & set([u'base', u'every'])):
|
if len(set(keys) & set(['base', 'every'])):
|
||||||
working_directory_trans = base_file_filter
|
working_directory_trans = base_file_filter
|
||||||
|
|
||||||
file_list_generator = working_list
|
file_list_generator = working_list
|
||||||
|
if 'all' in keys:
|
||||||
|
file_list_generator = all_list
|
||||||
if 'staging' in keys:
|
if 'staging' in keys:
|
||||||
file_list_generator = staging_list
|
file_list_generator = staging_list
|
||||||
|
|
||||||
return working_directory_trans(remove_submodules(file_list_generator))
|
return working_directory_trans(remove_submodules(file_list_generator))
|
||||||
|
|
||||||
|
|
||||||
|
# ___ _ _
|
||||||
|
# / __| |_ __ _ __ _(_)_ _ __ _ __ __ ___ _ __ _ _ __ _ __ ___ _ _
|
||||||
|
# \__ \ _/ _` / _` | | ' \/ _` | \ V V / '_/ _` | '_ \ '_ \/ -_) '_|
|
||||||
|
# |___/\__\__,_\__, |_|_||_\__, | \_/\_/|_| \__,_| .__/ .__/\___|_|
|
||||||
|
# |___/ |___/ |_| |_|
|
||||||
|
|
||||||
|
def pick_runner(options):
|
||||||
|
"""Choose a runner.
|
||||||
|
|
||||||
|
This is the operation that will run the linters. It exists to
|
||||||
|
provide a way to stash the repository, then restore it when
|
||||||
|
complete. If possible, it attempts to restore the access and
|
||||||
|
modification times of the file in order to comfort IDEs that are
|
||||||
|
constantly monitoring file times.
|
||||||
|
"""
|
||||||
|
|
||||||
def staging_wrapper(run_linters):
|
def staging_wrapper(run_linters):
|
||||||
def time_gather(f):
|
def time_gather(f):
|
||||||
stats = os.stat(f)
|
stats = os.stat(f)
|
||||||
return (f, (stats.atime, stats.mtime))
|
return (f, (stats.atime, stats.mtime))
|
||||||
|
|
||||||
times = [time_gather(file) for file in files]
|
times = [time_gather(file) for file in files]
|
||||||
run_git_command([u'stash', u'--keep-index'])
|
run_git_command(['stash', '--keep-index'])
|
||||||
|
|
||||||
results = run_linters()
|
results = run_linters()
|
||||||
run_git_command([u'reset', u'--hard'])
|
run_git_command(['reset', '--hard'])
|
||||||
run_git_command([u'stash', u'pop', u'--quiet', u'--index'])
|
run_git_command(['stash', 'pop', '--quiet', '--index'])
|
||||||
|
|
||||||
for (filename, timepair) in times:
|
for (filename, timepair) in times:
|
||||||
os.utime(filename, timepair)
|
os.utime(filename, timepair)
|
||||||
|
@ -226,110 +316,127 @@ def staging_wrapper(run_linters):
|
||||||
def workspace_wrapper(run_linters):
|
def workspace_wrapper(run_linters):
|
||||||
return run_linters()
|
return run_linters()
|
||||||
|
|
||||||
def pick_runner(options):
|
return staging in options.keys() and staging_wrapper or workspace_wrapper
|
||||||
if 'staging' in options.keys():
|
|
||||||
return staging_wrapper
|
|
||||||
return workspace_wrapper
|
|
||||||
|
|
||||||
|
|
||||||
|
# ___ _ _ _
|
||||||
|
# | _ \_ _ _ _ | (_)_ _| |_ _ __ __ _ ______
|
||||||
|
# | / || | ' \ | | | ' \ _| | '_ \/ _` (_-<_-<
|
||||||
|
# |_|_\\_,_|_||_| |_|_|_||_\__| | .__/\__,_/__/__/
|
||||||
|
# |_|
|
||||||
|
|
||||||
def run_external_linter(filename, linter):
|
def run_external_linter(filename, linter):
|
||||||
|
|
||||||
def _hy_anon_fn_83():
|
"""Run one linter against one file.
|
||||||
cmd = (((linter[u'command'] + u'"') + filename) + u'"')
|
|
||||||
(out, err, returncode) = get_shell_response(cmd)
|
If the result matches the error condition specified in the
|
||||||
(out, err, returncode)
|
configuration file, return the error code and messages, either
|
||||||
if ((out and (linter.get(u'condition', u'error') == u'output')) or err or (not (returncode == 0L))):
|
return nothing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def encode_shell_messages(prefix, messages):
|
||||||
|
return ['{}{}'.format(prefix, line.decode('utf-8'))
|
||||||
|
for line in messages.splitlines()]
|
||||||
|
|
||||||
|
|
||||||
|
cmd = linter['command'] + '"' + filename + '"'
|
||||||
|
(out, err, returncode) = get_shell_response(cmd)
|
||||||
|
if ((out and (linter.get('condition', 'error') == 'output')) or err or (not (returncode == 0L))):
|
||||||
|
prefix = linter.get('print', False) and '\t{}:'.format(filename) or '\t'
|
||||||
|
output = encode_shell_messages(prefix, out) + (err and encode_shell_messages(prefix, err) or [])
|
||||||
|
return ((returncode or 1), output)
|
||||||
|
return (0, [])
|
||||||
|
|
||||||
def _hy_anon_fn_82():
|
|
||||||
prefix = (u'\t{}:'.format(filename) if linter[u'print'] else u'\t')
|
|
||||||
output = (encode_shell_messages(prefix, out) + (encode_shell_messages(prefix, err) if err else []))
|
|
||||||
return [(returncode or 1L), output]
|
|
||||||
_hy_anon_var_11 = _hy_anon_fn_82()
|
|
||||||
else:
|
|
||||||
_hy_anon_var_11 = [0L, []]
|
|
||||||
return _hy_anon_var_11
|
|
||||||
return _hy_anon_fn_83()
|
|
||||||
|
|
||||||
def run_one_linter(linter, filenames):
|
def run_one_linter(linter, filenames):
|
||||||
|
|
||||||
def _hy_anon_fn_86():
|
""" Runs one linter against a set of files
|
||||||
match_filter = make_match_filter(linter)
|
|
||||||
config = linter.values()[0L]
|
Creates a match filter for the linter, extract the files to be
|
||||||
files = set(filter(match_filter, filenames))
|
linted, and runs the linter against each file, returning the
|
||||||
|
result as a list of successes and failures. Failures have a
|
||||||
|
return code and the output of the lint process.
|
||||||
|
"""
|
||||||
|
|
||||||
|
match_filter = make_match_filter(linter)
|
||||||
|
config = linter.values()[0]
|
||||||
|
files = set([file for file in filenames if match_filter(file)])
|
||||||
|
return [run_external_linter(file, config) for file in files]
|
||||||
|
|
||||||
def _hy_anon_fn_85(f):
|
|
||||||
return run_external_linter(f, config)
|
|
||||||
return list(map(_hy_anon_fn_85, files))
|
|
||||||
return _hy_anon_fn_86()
|
|
||||||
|
|
||||||
def build_lint_runner(linters, filenames):
|
def build_lint_runner(linters, filenames):
|
||||||
|
|
||||||
def _hy_anon_fn_90():
|
""" Returns a function to run a set of linters against a set of filenames
|
||||||
|
|
||||||
def _hy_anon_fn_89():
|
This returns a function because it's going to be wrapped in a
|
||||||
|
runner to better handle stashing and restoring a staged commit.
|
||||||
|
"""
|
||||||
|
def lint_runner():
|
||||||
keys = sorted(linters.keys())
|
keys = sorted(linters.keys())
|
||||||
|
return [run_one_linter({key: linters[key]}, filenames) for key in keys]
|
||||||
|
return lint_runner
|
||||||
|
|
||||||
def _hy_anon_fn_88(key):
|
|
||||||
return run_one_linter({key: linters[key], }, filenames)
|
|
||||||
return map(_hy_anon_fn_88, keys)
|
|
||||||
return _hy_anon_fn_89()
|
|
||||||
return _hy_anon_fn_90
|
|
||||||
|
|
||||||
def subset_config(config, keys):
|
# __ __ _
|
||||||
|
# | \/ |__ _(_)_ _
|
||||||
|
# | |\/| / _` | | ' \
|
||||||
|
# |_| |_\__,_|_|_||_|
|
||||||
|
#
|
||||||
|
|
||||||
def _hy_anon_fn_92():
|
|
||||||
ret = {}
|
|
||||||
for item in config.items():
|
|
||||||
if (item[0L] in keys):
|
|
||||||
ret[item[0L]] = item[1L]
|
|
||||||
_hy_anon_var_12 = None
|
|
||||||
else:
|
|
||||||
_hy_anon_var_12 = None
|
|
||||||
return ret
|
|
||||||
return _hy_anon_fn_92()
|
|
||||||
|
|
||||||
def run_gitlint(options, config, extras):
|
def run_gitlint(options, config, extras):
|
||||||
|
|
||||||
def _hy_anon_fn_94():
|
def build_config_subset(keys):
|
||||||
|
""" Returns a subset of the configuration, with only those linters mentioned in keys """
|
||||||
|
return {item[0]: item[1] for item in config.items() if item[0] in keys}
|
||||||
|
|
||||||
|
""" Runs the requested linters """
|
||||||
all_files = get_filelist(options)
|
all_files = get_filelist(options)
|
||||||
runner = pick_runner(options)
|
runner = pick_runner(options)
|
||||||
match_filter = make_match_filter(config)
|
lintable = make_match_filter(config)
|
||||||
lintable_files = set(filter(match_filter, all_files))
|
lintable_files = set([file for file in all_files if lintable(file)])
|
||||||
unlintables = (set(all_files) - lintable_files)
|
unlintables = (set(all_files) - lintable_files)
|
||||||
working_linters = get_working_linters(config)
|
working_linters = get_working_linters(config)
|
||||||
broken_linters = (set(config) - set(working_linters))
|
broken_linters = (set(config) - set(working_linters))
|
||||||
cant_lint_filter = make_match_filter(subset_config(config, broken_linters))
|
cant_lint = make_match_filter(subset_config(config, broken_linters))
|
||||||
cant_lintable = set(filter(cant_lint_filter, lintable_files))
|
cant_lintable = set([file for file in lintable_files if cant_lint(file)])
|
||||||
lint_runner = build_lint_runner(subset_config(config, working_linters), lintable_files)
|
lint_runner = build_lint_runner(build_config_subset(working_linters), lintable_files)
|
||||||
results = runner(lint_runner)
|
results = runner(lint_runner)
|
||||||
print(u'No Linter Available:', list(unlintables))
|
print(list(results))
|
||||||
print(u'Linter Executable Not Found for:', list(cant_lintable))
|
return results
|
||||||
return print(list(results))
|
|
||||||
return _hy_anon_fn_94()
|
|
||||||
|
|
||||||
def main(*args):
|
def main(*args):
|
||||||
|
if git_base is None:
|
||||||
|
sys.exit(_('Not currently in a git repository.'))
|
||||||
|
|
||||||
|
opts = RationalOptions(
|
||||||
|
optlist, args,
|
||||||
|
'git lint',
|
||||||
|
'Copyright (c) 2008, 2016 Kenneth M. "Elf" Sternberg <elf.sternberg@gmail.com>',
|
||||||
|
'0.0.4')
|
||||||
|
|
||||||
def _hy_anon_fn_97():
|
|
||||||
opts = hyopt(optlist, args, u'git lint', u'Copyright (c) 2008, 2016 Kenneth M. "Elf" Sternberg <elf.sternberg@gmail.com>', u'0.0.4')
|
|
||||||
if (git_base == None):
|
|
||||||
_hy_anon_var_14 = sys.exit(_(u'Not currently in a git repository.'))
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
|
|
||||||
def _hy_anon_fn_96():
|
|
||||||
options = opts.get_options()
|
options = opts.get_options()
|
||||||
config = get_config(options, git_base)
|
config = get_config(options, git_base)
|
||||||
return (opts.print_help() if options.has_key(u'help') else (opts.print_version() if options.has_key(u'version') else (print_linters(config) if options.has_key(u'linters') else (run_gitlint(options, config, opts.filenames) if True else None))))
|
if 'help' in options:
|
||||||
_hy_anon_var_13 = _hy_anon_fn_96()
|
opts.print_help()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if 'version' in options:
|
||||||
|
opts.print_version()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if 'linters' in options:
|
||||||
|
opts.print_linters()
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return run_gitlint(options, config, opts.filenames)
|
||||||
|
|
||||||
except getopt.GetoptError as err:
|
except getopt.GetoptError as err:
|
||||||
_hy_anon_var_13 = opts.print_help()
|
opts.print_help()
|
||||||
_hy_anon_var_14 = _hy_anon_var_13
|
return 0
|
||||||
return _hy_anon_var_14
|
|
||||||
return _hy_anon_fn_97()
|
|
||||||
if (__name__ == u'__main__'):
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
:G_1235 = main(*sys.argv)
|
sys.exit(main(*sys.argv))
|
||||||
_hy_anon_var_15 = (sys.exit(:G_1235) if is_integer(:G_1235) else None)
|
|
||||||
else:
|
|
||||||
_hy_anon_var_15 = None
|
|
||||||
|
|
|
@ -6,7 +6,21 @@ import ConfigParser
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
|
||||||
def _find_config_file(options, base):
|
# (commandLineDictionary, repositoryLocation) -> (configurationFilePath | exit)
|
||||||
|
|
||||||
|
def find_config_file(options, base):
|
||||||
|
""" Returns the configuration file from a prioritized list of locations.
|
||||||
|
|
||||||
|
Locations are prioritized as:
|
||||||
|
1. From the command line. Fail if specified but not found
|
||||||
|
2. The repository's root directory, as the file .git-lint
|
||||||
|
3. The repository's root directory, as the file .git-lint/config
|
||||||
|
4. The user's home directory, as file .git-lint
|
||||||
|
5. The user's home directory, as the file .git-lint/config
|
||||||
|
|
||||||
|
If no configuration file is found, this is an error.
|
||||||
|
"""
|
||||||
|
|
||||||
if 'config' in options:
|
if 'config' in options:
|
||||||
config = options['config']
|
config = options['config']
|
||||||
configpath = os.path.abspath(config)
|
configpath = os.path.abspath(config)
|
||||||
|
@ -27,10 +41,23 @@ def _find_config_file(options, base):
|
||||||
return matches[0]
|
return matches[0]
|
||||||
|
|
||||||
|
|
||||||
|
# (commandLineDictionary, repositoryLocation) -> (configurationDictionary | exit)
|
||||||
|
|
||||||
def get_config(options, base):
|
def get_config(options, base):
|
||||||
|
"""Loads the git-lint configuration file.
|
||||||
|
|
||||||
|
Returns the configuration file as a dictionary of dictionaries.
|
||||||
|
Performs substitutions as specified in the SafeConfigParser
|
||||||
|
specification; the only one performed currently is the 'repodir'
|
||||||
|
will be replaced with the base directory of the repository.
|
||||||
|
Combined with the option to specify the .git-lint configuration as
|
||||||
|
a directory, this allows users to keep per-project configuration
|
||||||
|
files for specific linters.
|
||||||
|
"""
|
||||||
|
|
||||||
path = find_config_file(options, base)
|
path = find_config_file(options, base)
|
||||||
configloader = ConfigParser.SafeConfigParser()
|
configloader = ConfigParser.SafeConfigParser()
|
||||||
configloader.read(path)
|
configloader.read(path)
|
||||||
configloader.set('DEFAULT', 'repdir', base)
|
configloader.set('DEFAULT', 'repodir', base)
|
||||||
return {section: {k, v for (k, v) in configloader.items(section)}
|
return {section: {k: v for (k, v) in configloader.items(section)}
|
||||||
for section in configloader.sections()}
|
for section in configloader.sections()}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import gettext
|
||||||
_ = gettext.gettext
|
_ = gettext.gettext
|
||||||
|
|
||||||
|
|
||||||
|
# -> scriptNameString
|
||||||
|
|
||||||
def get_script_name():
|
def get_script_name():
|
||||||
|
""" Returns the best possible name for the script. """
|
||||||
if getattr(sys, 'frozen', False):
|
if getattr(sys, 'frozen', False):
|
||||||
(path, name) = os.path.split(sys.executable)
|
(path, name) = os.path.split(sys.executable)
|
||||||
return name
|
return name
|
||||||
|
@ -19,6 +22,8 @@ def get_script_name():
|
||||||
return names.pop()
|
return names.pop()
|
||||||
|
|
||||||
|
|
||||||
|
# OptionTupleList -> (getOptOptions -> dictionaryOfOptions)
|
||||||
|
|
||||||
def make_options_rationalizer(optlist):
|
def make_options_rationalizer(optlist):
|
||||||
"""Takes a list of option tuples, and returns a function that takes
|
"""Takes a list of option tuples, and returns a function that takes
|
||||||
the output of getopt and reduces it to the longopt key and
|
the output of getopt and reduces it to the longopt key and
|
||||||
|
@ -46,6 +51,8 @@ def make_options_rationalizer(optlist):
|
||||||
return rationalizer
|
return rationalizer
|
||||||
|
|
||||||
|
|
||||||
|
# (OptionTupleList, dictionaryOfOptions) -> (dictionaryOfOptions, excludedOptions)
|
||||||
|
|
||||||
def remove_conflicted_options(optlist, request):
|
def remove_conflicted_options(optlist, request):
|
||||||
"""Takes our list of option tuples, and a cleaned copy of what was
|
"""Takes our list of option tuples, and a cleaned copy of what was
|
||||||
requested from getopt, and returns a copy of the request
|
requested from getopt, and returns a copy of the request
|
||||||
|
@ -102,7 +109,7 @@ class RationalOptions:
|
||||||
def print_help(self):
|
def print_help(self):
|
||||||
print(_('Usage: {} [options] [filenames]').format(self.name))
|
print(_('Usage: {} [options] [filenames]').format(self.name))
|
||||||
for item in self.optlist:
|
for item in self.optlist:
|
||||||
print(' -{:<1} --{:<12} {}'.format(item[0L], item[1L], item[3L]))
|
print(' -{:<1} --{:<12} {}'.format(item[0], item[1], item[3]))
|
||||||
return sys.exit()
|
return sys.exit()
|
||||||
|
|
||||||
def print_version(self):
|
def print_version(self):
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
# foo!
|
|
|
@ -1,287 +0,0 @@
|
||||||
; -*- mode: clojure -*-
|
|
||||||
(import os re subprocess sys gettext)
|
|
||||||
(def *version* "0.0.2")
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
|
|
||||||
; 0: Short opt, 1: long opt, 2: takes argument, 3: help text
|
|
||||||
(def optlist [["o" "only" true (_ "A comma-separated list of only those linters to run") ["x"]]
|
|
||||||
["x" "exclude" true (_ "A comma-separated list of linters to skip") []]
|
|
||||||
["b" "base" false (_ "Check all changed files in the repository, not just those in the current directory.") []]
|
|
||||||
["a" "all" false (_ "Scan all files in the repository, not just those that have changed.")]
|
|
||||||
["w" "workspace" false (_ "Scan the workspace") ["s"]]
|
|
||||||
["s" "staging" false (_ "Scan the staging area (pre-commit).") []]
|
|
||||||
["g" "changes" false (_ "Report lint failures only for diff'd sections") ["l"]]
|
|
||||||
["l" "complete" false (_ "Report lint failures for all files") []]
|
|
||||||
["c" "config" true (_ "Path to config file") []]
|
|
||||||
["h" "help" false (_ "This help message") []]
|
|
||||||
["v" "version" false (_"Version information") []]])
|
|
||||||
|
|
||||||
; Given a set of command-line arguments, compare that to a mapped
|
|
||||||
; version of the optlist and return a canonicalized dictionary of all
|
|
||||||
; the arguments that have been set. For example "-c" and "--config"
|
|
||||||
; will both be mapped to "config".
|
|
||||||
|
|
||||||
; Given a prefix of one or two dashes and a position in the above
|
|
||||||
; array, creates a function to map either the short or long option
|
|
||||||
; to the option name.
|
|
||||||
|
|
||||||
(defn make-opt-assoc [prefix pos]
|
|
||||||
(fn [acc it] (assoc acc (+ prefix (get it pos)) (get it 1)) acc))
|
|
||||||
|
|
||||||
; Using the above, create a full map of all arguments, then return a
|
|
||||||
; function ready to look up any argument and return the option name.
|
|
||||||
|
|
||||||
(defn make-options-rationalizer [optlist]
|
|
||||||
(let [
|
|
||||||
[short-opt-assoc (make-opt-assoc "-" 0)]
|
|
||||||
[long-opt-assoc (make-opt-assoc "--" 1)]
|
|
||||||
[fullset
|
|
||||||
(ap-reduce (-> (short-opt-assoc acc it)
|
|
||||||
(long-opt-assoc it)) optlist {})]]
|
|
||||||
(fn [acc it] (do (assoc acc (get fullset (get it 0)) (get it 1)) acc))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn print-version []
|
|
||||||
(print (.format "git-lint (hy version {})" *version*))
|
|
||||||
(print "Copyright (c) 2008, 2014 Kenneth M. \"Elf\" Sternberg <elf.sternberg@gmail.com>")
|
|
||||||
(sys.exit))
|
|
||||||
|
|
||||||
(defn print-help []
|
|
||||||
(print "Usage: git lint [options] [filename]")
|
|
||||||
(ap-each optlist (print (.format " -{} --{} {}" (get it 0) (get it 1) (get it 3))))
|
|
||||||
(sys.exit))
|
|
||||||
|
|
||||||
; `lint` should be a directory under your .git where you store the RCs
|
|
||||||
; for your various linters. If you want to use a global one, you'll
|
|
||||||
; have to edit the configuration entries below.
|
|
||||||
|
|
||||||
(def *config-path*
|
|
||||||
|
|
||||||
|
|
||||||
(os.path.join (.get os.environ "GIT_DIR" "./.git") "lint"))
|
|
||||||
|
|
||||||
(def *git-modified-pattern* (.compile re "^[MA]\s+(?P<name>.*)$"))
|
|
||||||
|
|
||||||
(def *checks*
|
|
||||||
[
|
|
||||||
; {
|
|
||||||
; "output" "Checking for debugger commands in Javascript..."
|
|
||||||
; "command" "grep -n debugger {filename}"
|
|
||||||
; "match_files" [".*\.js$"]
|
|
||||||
; "print_filename" True
|
|
||||||
; "error_condition" "output"
|
|
||||||
; }
|
|
||||||
{
|
|
||||||
"output" "Running Jshint..."
|
|
||||||
"command" "jshint -c {config_path}/jshint.rc {filename}"
|
|
||||||
"match_files" [".*\.js$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running Coffeelint..."
|
|
||||||
"command" "coffeelint {filename}"
|
|
||||||
"match_files" [".*\.coffee$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running JSCS..."
|
|
||||||
"command" "jscs -c {config_path}/jscs.rc {filename}"
|
|
||||||
"match_files" [".*\.js$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running pep8..."
|
|
||||||
"command" "pep8 -r --ignore=E501,W293,W391 {filename}"
|
|
||||||
"match_files" [".*\.py$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running xmllint..."
|
|
||||||
"command" "xmllint {filename}"
|
|
||||||
"match_files" [".*\.xml"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn get-git-response [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]
|
|
||||||
[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn run-git-command [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]]
|
|
||||||
(subprocess.call fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)))
|
|
||||||
|
|
||||||
(defn get-shell-response [fullcmd]
|
|
||||||
(let [[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE
|
|
||||||
:shell True)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn derive-max-code [code-pairs]
|
|
||||||
(reduce
|
|
||||||
(fn [m i] (if (> (abs (get i 0)) (abs m)) (get i 0) m))
|
|
||||||
code-pairs 0))
|
|
||||||
|
|
||||||
(defn derive-message-bodies [code-pairs]
|
|
||||||
(lmap (fn [i] (get i 1)) code-pairs))
|
|
||||||
|
|
||||||
(defn lmap (pred iter) (list (map pred iter)))
|
|
||||||
|
|
||||||
(defn encode-shell-messages [prefix messages]
|
|
||||||
(lmap (fn [line] (.format "{}{}" prefix (.decode line "utf-8")))
|
|
||||||
(.splitlines messages)))
|
|
||||||
|
|
||||||
(defn run-external-checker [filename check]
|
|
||||||
(let [[cmd (-> (get check "command")
|
|
||||||
(.format
|
|
||||||
:filename filename
|
|
||||||
:config_path *config-path*))]
|
|
||||||
[(, out err returncode) (get-shell-response cmd)]]
|
|
||||||
(if (or (and out (= (.get check "error_condition" "error") "output"))
|
|
||||||
err
|
|
||||||
(not (= returncode 0)))
|
|
||||||
(let [[prefix (if (get check "print_filename")
|
|
||||||
(.format "\t{}:" filename)
|
|
||||||
"\t")]
|
|
||||||
[output (+ (encode-shell-messages prefix out)
|
|
||||||
(if err (encode-shell-messages prefix err) []))]]
|
|
||||||
[(or returncode 1) output])
|
|
||||||
[0 []])))
|
|
||||||
|
|
||||||
(defn matches-file [filename match-files]
|
|
||||||
(any (map (fn [match-file] (-> (.compile re match-file)
|
|
||||||
(.match filename)))
|
|
||||||
match-files)))
|
|
||||||
|
|
||||||
(defn check-scan-wanted [filename check]
|
|
||||||
(cond [(and (in "match_files" check)
|
|
||||||
(not (matches-file filename (get check "match_files")))) false]
|
|
||||||
[(and (in "ignore_files" check)
|
|
||||||
(matches-file filename (get check "ignore_files"))) false]
|
|
||||||
[true true]))
|
|
||||||
|
|
||||||
(defn check-files [filenames check]
|
|
||||||
(let [[filenames-to-check
|
|
||||||
(filter (fn [filename] (check-scan-wanted filename check)) filenames)]
|
|
||||||
[results-of-checks
|
|
||||||
(lmap (fn [filename]
|
|
||||||
(run-external-checker filename check)) filenames-to-check)]
|
|
||||||
[messages (+ [(get check "output")]
|
|
||||||
(derive-message-bodies results-of-checks))]]
|
|
||||||
[(derive-max-code results-of-checks) messages]))
|
|
||||||
|
|
||||||
(defn gather-all-filenames []
|
|
||||||
(let [[build-filenames
|
|
||||||
(fn [filenames]
|
|
||||||
(map
|
|
||||||
(fn [f] (os.path.join (get filenames 0) f)) (get filenames 2)))]]
|
|
||||||
(list
|
|
||||||
(flatten
|
|
||||||
(list-comp (build-filenames o) [o (.walk os ".")])))))
|
|
||||||
|
|
||||||
(defn gather-staged-filenames [against]
|
|
||||||
(let [[(, out err returncode)
|
|
||||||
(get-git-response ["diff-index" "--name-status" against])]
|
|
||||||
[lines (.splitlines out)]
|
|
||||||
[matcher
|
|
||||||
(fn [line]
|
|
||||||
(.match *git-modified-pattern* (.decode line "utf-8")))]]
|
|
||||||
(list
|
|
||||||
(filter
|
|
||||||
(fn [x] (not (= x "")))
|
|
||||||
(list-comp (.group match "name") [match (map matcher lines)] match)))))
|
|
||||||
|
|
||||||
(defn run-checks-for [scan-all-files against]
|
|
||||||
(do
|
|
||||||
(run-git-command ["stash" "--keep-index"])
|
|
||||||
(let [[filenames-to-scan
|
|
||||||
(if scan-all-files
|
|
||||||
(gather-all-filenames)
|
|
||||||
(gather-staged-filenames against))]
|
|
||||||
[results-of-scan
|
|
||||||
(lmap (fn [check] (check-files filenames-to-scan check)) *checks*)]
|
|
||||||
[exit-code (derive-max-code results-of-scan)]
|
|
||||||
[messages (flatten (derive-message-bodies results-of-scan))]]
|
|
||||||
(do
|
|
||||||
(for [line messages] (print line))
|
|
||||||
(run-git-command ["reset" "--hard"])
|
|
||||||
(run-git-command ["stash" "pop" "--quiet" "--index"])
|
|
||||||
exit-code))))
|
|
||||||
|
|
||||||
(defn get-head-tag []
|
|
||||||
(let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
|
|
||||||
[(, out err returncode) (get-git-response ["rev-parse" "--verify HEAD"])]]
|
|
||||||
(if err empty-repository-hash "HEAD")))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(let [[scan-all-files (and (> (len args) 1) (= (get args 2) "--all-files"))]]
|
|
||||||
(sys.exit (int (run-checks-for scan-all-files (get-head-tag))))))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(try
|
|
||||||
(let [[optstringsshort
|
|
||||||
(string.join (ap-map (+ (. it [0]) (cond [(. it [2]) ":"] [true ""])) optlist) "")]
|
|
||||||
[optstringslong
|
|
||||||
(list (ap-map (+ (. it [1]) (cond [(. it [2]) "="] [true ""])) optlist))]
|
|
||||||
[(, opt arg)
|
|
||||||
(getopt.getopt (slice args 1) optstringsshort optstringslong)]
|
|
||||||
[rationalize-options
|
|
||||||
(make-options-rationalizer optlist)]
|
|
||||||
[options
|
|
||||||
(sanify-options (ap-reduce (rationalize-options acc it) opt {}))]]
|
|
||||||
|
|
||||||
|
|
||||||
(cond [(.has_key options "help") (print-help)]
|
|
||||||
[(.has_key options "version") (print-version)]
|
|
||||||
[true (suggest options)]))
|
|
||||||
(catch [err getopt.GetoptError]
|
|
||||||
(print (str err))
|
|
||||||
(print-help))))
|
|
||||||
|
|
||||||
; staging or workspace
|
|
||||||
; if workspace:
|
|
||||||
; modified or all
|
|
||||||
; CWD or base
|
|
||||||
|
|
||||||
(defn get-porcelain-status [cmd]
|
|
||||||
(let [[stream (.split (get-git-response ["status" "--porcelain" "--untracked-files=all" "--ignore-submodules=all"]) "\0")]
|
|
||||||
[parse-stream (fn [acc stream]
|
|
||||||
(if (= 0 (len stream))
|
|
||||||
acc
|
|
||||||
(let [[temp (.pop stream 0)]
|
|
||||||
[index (.pop temp 0)]
|
|
||||||
[workspace (.pop temp 0)]
|
|
||||||
[filename (slice temp 1)]]
|
|
||||||
(if (= index "R")
|
|
||||||
(.pop stream 0))
|
|
||||||
(parse-stream (.append acc (, index workspace filename)) stream))))]]
|
|
||||||
(parse-stream [] stream)))
|
|
||||||
|
|
||||||
(defn modified-in-workspace [s] (s[0] in ["M" "A" "?"]))
|
|
||||||
(defn modified-in-staging [s] (s[1] in ["M" "A" "?"]))
|
|
||||||
(defn get-name [s] (s[2]))
|
|
||||||
|
|
||||||
(defn run-staged-scan [options]
|
|
||||||
(let [[to-scan (filter (fn [a] (in (get (get a 0) 0) ["R" "M"]))
|
|
||||||
|
|
||||||
(defn get-head-tag []
|
|
||||||
(let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
|
|
||||||
[(, out err returncode) (get-git-response ["rev-parse" "--verify HEAD"])]]
|
|
||||||
(if err empty-repository-hash "HEAD")))
|
|
||||||
|
|
|
@ -1,255 +0,0 @@
|
||||||
#!/usr/bin/env hy
|
|
||||||
(import os re subprocess sys gettext)
|
|
||||||
(def *version* "0.0.2")
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
|
|
||||||
; 0: Short opt, 1: long opt, 2: takes argument, 3: help text
|
|
||||||
(def optlist [["o" "only" true (_ "A comma-separated list of only those linters to run") ["x"]]
|
|
||||||
["x" "exclude" true (_ "A comma-separated list of linters to skip") []]
|
|
||||||
["b" "base" false (_ "Check all changed files in the repository, not just those in the current directory.") []]
|
|
||||||
["a" "all" false (_ "Scan all files in the repository, not just those that have changed.")]
|
|
||||||
["w" "workspace" false (_ "Scan the workspace") ["s"]]
|
|
||||||
["s" "staging" false (_ "Scan the staging area (pre-commit).") []]
|
|
||||||
["g" "changes" false (_ "Report lint failures only for diff'd sections") ["l"]]
|
|
||||||
["l" "complete" false (_ "Report lint failures for all files") []]
|
|
||||||
["c" "config" true (_ "Path to config file") []]
|
|
||||||
["h" "help" false (_ "This help message") []]
|
|
||||||
["v" "version" false (_"Version information") []]])
|
|
||||||
|
|
||||||
; Given a set of command-line arguments, compare that to a mapped
|
|
||||||
; version of the optlist and return a canonicalized dictionary of all
|
|
||||||
; the arguments that have been set. For example "-c" and "--config"
|
|
||||||
; will both be mapped to "config".
|
|
||||||
|
|
||||||
; Given a prefix of one or two dashes and a position in the above
|
|
||||||
; array, creates a function to map either the short or long option
|
|
||||||
; to the option name.
|
|
||||||
|
|
||||||
(defn make-opt-assoc [prefix pos]
|
|
||||||
(fn [acc it] (assoc acc (+ prefix (get it pos)) (get it 1)) acc))
|
|
||||||
|
|
||||||
; Using the above, create a full map of all arguments, then return a
|
|
||||||
; function ready to look up any argument and return the option name.
|
|
||||||
|
|
||||||
(defn make-options-rationalizer [optlist]
|
|
||||||
(let [
|
|
||||||
[short-opt-assoc (make-opt-assoc "-" 0)]
|
|
||||||
[long-opt-assoc (make-opt-assoc "--" 1)]
|
|
||||||
[fullset
|
|
||||||
(ap-reduce (-> (short-opt-assoc acc it)
|
|
||||||
(long-opt-assoc it)) optlist {})]]
|
|
||||||
(fn [acc it] (do (assoc acc (get fullset (get it 0)) (get it 1)) acc))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn print-version []
|
|
||||||
(print (.format "git-lint (hy version {})" *version*))
|
|
||||||
(print "Copyright (c) 2008, 2014 Kenneth M. \"Elf\" Sternberg <elf.sternberg@gmail.com>")
|
|
||||||
(sys.exit))
|
|
||||||
|
|
||||||
(defn print-help []
|
|
||||||
(print "Usage: git lint [options] [filename]")
|
|
||||||
(ap-each optlist (print (.format " -{} --{} {}" (get it 0) (get it 1) (get it 3))))
|
|
||||||
(sys.exit))
|
|
||||||
|
|
||||||
; `lint` should be a directory under your .git where you store the RCs
|
|
||||||
; for your various linters. If you want to use a global one, you'll
|
|
||||||
; have to edit the configuration entries below.
|
|
||||||
|
|
||||||
(def *config-path*
|
|
||||||
|
|
||||||
|
|
||||||
(os.path.join (.get os.environ "GIT_DIR" "./.git") "lint"))
|
|
||||||
|
|
||||||
(def *git-modified-pattern* (.compile re "^[MA]\s+(?P<name>.*)$"))
|
|
||||||
|
|
||||||
(def *checks*
|
|
||||||
[
|
|
||||||
; {
|
|
||||||
; "output" "Checking for debugger commands in Javascript..."
|
|
||||||
; "command" "grep -n debugger {filename}"
|
|
||||||
; "match_files" [".*\.js$"]
|
|
||||||
; "print_filename" True
|
|
||||||
; "error_condition" "output"
|
|
||||||
; }
|
|
||||||
{
|
|
||||||
"output" "Running Jshint..."
|
|
||||||
"command" "jshint -c {config_path}/jshint.rc {filename}"
|
|
||||||
"match_files" [".*\.js$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running Coffeelint..."
|
|
||||||
"command" "coffeelint {filename}"
|
|
||||||
"match_files" [".*\.coffee$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running JSCS..."
|
|
||||||
"command" "jscs -c {config_path}/jscs.rc {filename}"
|
|
||||||
"match_files" [".*\.js$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running pep8..."
|
|
||||||
"command" "pep8 -r --ignore=E501,W293,W391 {filename}"
|
|
||||||
"match_files" [".*\.py$"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
{
|
|
||||||
"output" "Running xmllint..."
|
|
||||||
"command" "xmllint {filename}"
|
|
||||||
"match_files" [".*\.xml"]
|
|
||||||
"print_filename" False
|
|
||||||
"error_condition" "error"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
(defn get-git-response [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]
|
|
||||||
[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn run-git-command [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]]
|
|
||||||
(subprocess.call fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)))
|
|
||||||
|
|
||||||
(defn get-shell-response [fullcmd]
|
|
||||||
(let [[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE
|
|
||||||
:shell True)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn derive-max-code [code-pairs]
|
|
||||||
(reduce
|
|
||||||
(fn [m i] (if (> (abs (get i 0)) (abs m)) (get i 0) m))
|
|
||||||
code-pairs 0))
|
|
||||||
|
|
||||||
(defn derive-message-bodies [code-pairs]
|
|
||||||
(lmap (fn [i] (get i 1)) code-pairs))
|
|
||||||
|
|
||||||
(defn lmap (pred iter) (list (map pred iter)))
|
|
||||||
|
|
||||||
(defn encode-shell-messages [prefix messages]
|
|
||||||
(lmap (fn [line] (.format "{}{}" prefix (.decode line "utf-8")))
|
|
||||||
(.splitlines messages)))
|
|
||||||
|
|
||||||
(defn run-external-checker [filename check]
|
|
||||||
(let [[cmd (-> (get check "command")
|
|
||||||
(.format
|
|
||||||
:filename filename
|
|
||||||
:config_path *config-path*))]
|
|
||||||
[(, out err returncode) (get-shell-response cmd)]]
|
|
||||||
(if (or (and out (= (.get check "error_condition" "error") "output"))
|
|
||||||
err
|
|
||||||
(not (= returncode 0)))
|
|
||||||
(let [[prefix (if (get check "print_filename")
|
|
||||||
(.format "\t{}:" filename)
|
|
||||||
"\t")]
|
|
||||||
[output (+ (encode-shell-messages prefix out)
|
|
||||||
(if err (encode-shell-messages prefix err) []))]]
|
|
||||||
[(or returncode 1) output])
|
|
||||||
[0 []])))
|
|
||||||
|
|
||||||
(defn matches-file [filename match-files]
|
|
||||||
(any (map (fn [match-file] (-> (.compile re match-file)
|
|
||||||
(.match filename)))
|
|
||||||
match-files)))
|
|
||||||
|
|
||||||
(defn check-scan-wanted [filename check]
|
|
||||||
(cond [(and (in "match_files" check)
|
|
||||||
(not (matches-file filename (get check "match_files")))) false]
|
|
||||||
[(and (in "ignore_files" check)
|
|
||||||
(matches-file filename (get check "ignore_files"))) false]
|
|
||||||
[true true]))
|
|
||||||
|
|
||||||
(defn check-files [filenames check]
|
|
||||||
(let [[filenames-to-check
|
|
||||||
(filter (fn [filename] (check-scan-wanted filename check)) filenames)]
|
|
||||||
[results-of-checks
|
|
||||||
(lmap (fn [filename]
|
|
||||||
(run-external-checker filename check)) filenames-to-check)]
|
|
||||||
[messages (+ [(get check "output")]
|
|
||||||
(derive-message-bodies results-of-checks))]]
|
|
||||||
[(derive-max-code results-of-checks) messages]))
|
|
||||||
|
|
||||||
(defn gather-all-filenames []
|
|
||||||
(let [[build-filenames
|
|
||||||
(fn [filenames]
|
|
||||||
(map
|
|
||||||
(fn [f] (os.path.join (get filenames 0) f)) (get filenames 2)))]]
|
|
||||||
(list
|
|
||||||
(flatten
|
|
||||||
(list-comp (build-filenames o) [o (.walk os ".")])))))
|
|
||||||
|
|
||||||
(defn gather-staged-filenames [against]
|
|
||||||
(let [[(, out err returncode)
|
|
||||||
(get-git-response ["diff-index" "--name-status" against])]
|
|
||||||
[lines (.splitlines out)]
|
|
||||||
[matcher
|
|
||||||
(fn [line]
|
|
||||||
(.match *git-modified-pattern* (.decode line "utf-8")))]]
|
|
||||||
(list
|
|
||||||
(filter
|
|
||||||
(fn [x] (not (= x "")))
|
|
||||||
(list-comp (.group match "name") [match (map matcher lines)] match)))))
|
|
||||||
|
|
||||||
(defn run-checks-for [scan-all-files against]
|
|
||||||
(do
|
|
||||||
(run-git-command ["stash" "--keep-index"])
|
|
||||||
(let [[filenames-to-scan
|
|
||||||
(if scan-all-files
|
|
||||||
(gather-all-filenames)
|
|
||||||
(gather-staged-filenames against))]
|
|
||||||
[results-of-scan
|
|
||||||
(lmap (fn [check] (check-files filenames-to-scan check)) *checks*)]
|
|
||||||
[exit-code (derive-max-code results-of-scan)]
|
|
||||||
[messages (flatten (derive-message-bodies results-of-scan))]]
|
|
||||||
(do
|
|
||||||
(for [line messages] (print line))
|
|
||||||
(run-git-command ["reset" "--hard"])
|
|
||||||
(run-git-command ["stash" "pop" "--quiet" "--index"])
|
|
||||||
exit-code))))
|
|
||||||
|
|
||||||
(defn get-head-tag []
|
|
||||||
(let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
|
|
||||||
[(, out err returncode) (get-git-response ["rev-parse" "--verify HEAD"])]]
|
|
||||||
(if err empty-repository-hash "HEAD")))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(let [[scan-all-files (and (> (len args) 1) (= (get args 2) "--all-files"))]]
|
|
||||||
(sys.exit (int (run-checks-for scan-all-files (get-head-tag))))))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(try
|
|
||||||
(let [[optstringsshort
|
|
||||||
(string.join (ap-map (+ (. it [0]) (cond [(. it [2]) ":"] [true ""])) optlist) "")]
|
|
||||||
[optstringslong
|
|
||||||
(list (ap-map (+ (. it [1]) (cond [(. it [2]) "="] [true ""])) optlist))]
|
|
||||||
[(, opt arg)
|
|
||||||
(getopt.getopt (slice args 1) optstringsshort optstringslong)]
|
|
||||||
[rationalize-options
|
|
||||||
(make-options-rationalizer optlist)]
|
|
||||||
[options
|
|
||||||
(sanify-options (ap-reduce (rationalize-options acc it) opt {}))]]
|
|
||||||
|
|
||||||
|
|
||||||
(cond [(.has_key options "help") (print-help)]
|
|
||||||
[(.has_key options "version") (print-version)]
|
|
||||||
[true (suggest options)]))
|
|
||||||
(catch [err getopt.GetoptError]
|
|
||||||
(print (str err))
|
|
||||||
(print-help))))
|
|
|
@ -1,330 +0,0 @@
|
||||||
#!/usr/bin/env hy ; -*- mode: clojure -*-
|
|
||||||
(import ConfigParser os subprocess operator re gettext sys getopt)
|
|
||||||
(.append sys.path "Users/ksternberg/build/git-lint/git_lint_src")
|
|
||||||
(import [git-lint-options [hyopt]])
|
|
||||||
(import [git-lint-config [get-config]])
|
|
||||||
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
(def *version* "0.0.2")
|
|
||||||
|
|
||||||
(defn tap [a] (print "TAP:" a) a)
|
|
||||||
; short-name long-name takes-args description precludes
|
|
||||||
(def optlist [["o" "only" true (_ "A comma-separated list of only those linters to run") ["exclude"]]
|
|
||||||
["x" "exclude" true (_ "A comma-separated list of linters to skip") []]
|
|
||||||
["l" "linters" false (_ "Show the list of configured linters")]
|
|
||||||
["b" "base" false (_ "Check all changed files in the repository, not just those in the current directory.") []]
|
|
||||||
["a" "all" false (_ "Scan all files in the repository, not just those that have changed.")]
|
|
||||||
["e" "every" false (_ "Short for -b -a: scan everything")]
|
|
||||||
["w" "workspace" false (_ "Scan the workspace") ["staging"]]
|
|
||||||
["s" "staging" false (_ "Scan the staging area (useful for pre-commit).") []]
|
|
||||||
["g" "changes" false (_ "Report lint failures only for diff'd sections") ["complete"]]
|
|
||||||
["p" "complete" false (_ "Report lint failures for all files") []]
|
|
||||||
["c" "config" true (_ "Path to config file") []]
|
|
||||||
["h" "help" false (_ "This help message") []]
|
|
||||||
["v" "version" false (_"Version information") []]])
|
|
||||||
|
|
||||||
(defn get-git-response-raw [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]
|
|
||||||
[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn get-git-response [cmd]
|
|
||||||
(let [[(, out error returncode) (get-git-response-raw cmd)]] out))
|
|
||||||
|
|
||||||
(defn split-git-response [cmd]
|
|
||||||
(let [[(, out error returncode) (get-git-response-raw cmd)]] (.splitlines out)))
|
|
||||||
|
|
||||||
(defn run-git-command [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]]
|
|
||||||
(subprocess.call fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)))
|
|
||||||
|
|
||||||
(defn get-shell-response [fullcmd]
|
|
||||||
(let [[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE
|
|
||||||
:shell True)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(def git-base (let [[(, out error returncode)
|
|
||||||
(get-git-response-raw ["rev-parse" "--show-toplevel"])]]
|
|
||||||
(if (not (= returncode 0)) None (.rstrip out))))
|
|
||||||
|
|
||||||
; That mystery number is the precise hash code for a repository for has been initialized,
|
|
||||||
; but for which nothing has ever been added or committed. An empty repository has no refs
|
|
||||||
; at all so you can't use HEAD in this one very rare case.
|
|
||||||
(def git-head
|
|
||||||
(let [[empty-repository-hash "4b825dc642cb6eb9a060e54bf8d69288fbee4904"]
|
|
||||||
[(, out err returncode) (get-git-response-raw ["rev-parse" "--verify HEAD"])]]
|
|
||||||
(if (not err) "HEAD" empty-repository-hash)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn run-external-checker [path config]
|
|
||||||
(let [[cmd (-> (get config "command")
|
|
||||||
(.format (+ command " \"{}\"") path))]
|
|
||||||
[(, out err returncode) (get-shell-response cmd)]]
|
|
||||||
(if (or (and out (= (.get check "error_condition" "error") "output"))
|
|
||||||
err
|
|
||||||
(not (= returncode 0)))
|
|
||||||
(let [[prefix (if (get check "print_filename")
|
|
||||||
(.format "\t{}:" filename)
|
|
||||||
"\t")]
|
|
||||||
[output (+ (encode-shell-messages prefix out)
|
|
||||||
(if err (encode-shell-messages prefix err) []))]]
|
|
||||||
[(or returncode 1) output])
|
|
||||||
[0 []])))
|
|
||||||
|
|
||||||
; ___ _ _ ___ _ _ ___ _ _
|
|
||||||
;| __(_) |___ _ _ __ _ _ __ ___ | __|_ _| |_ ___ _ _ __(_)___ _ _ / __| |_ ___ __| |__
|
|
||||||
;| _|| | / -_) ' \/ _` | ' \/ -_) | _|\ \ / _/ -_) ' \(_-< / _ \ ' \ | (__| ' \/ -_) _| / /
|
|
||||||
;|_| |_|_\___|_||_\__,_|_|_|_\___| |___/_\_\\__\___|_||_/__/_\___/_||_| \___|_||_\___\__|_\_\
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn make-match-filter-matcher [extensions]
|
|
||||||
(->> (map (fn [s] (.split s ",")) extensions)
|
|
||||||
(reduce operator.add)
|
|
||||||
(map (fn [s] (.strip s)))
|
|
||||||
(set)
|
|
||||||
(filter (fn [s] (not (= 0 (len s)))))
|
|
||||||
(map (fn [s] (.sub re "^\." "" s)))
|
|
||||||
(.join "|")
|
|
||||||
((fn [s] (+ "\.(" s ")$")))
|
|
||||||
((fn [s] (re.compile s re.I)))))
|
|
||||||
|
|
||||||
(defn make-match-filter [config]
|
|
||||||
(let [[matcher (make-match-filter-matcher (map (fn [v] (.get v "match" "" )) (.itervalues config)))]]
|
|
||||||
(fn [path] (.search matcher path))))
|
|
||||||
|
|
||||||
; _ _ _ _ _ _ _ _
|
|
||||||
;| | (_)_ _| |_ ___ _ _ _____ _____ __ _ _| |_ __ _| |__| |___ __| |_ __ _| |_ _ _ ___
|
|
||||||
;| |__| | ' \ _/ -_) '_| / -_) \ / -_) _| || | _/ _` | '_ \ / -_) (_-< _/ _` | _| || (_-<
|
|
||||||
;|____|_|_||_\__\___|_| \___/_\_\___\__|\_,_|\__\__,_|_.__/_\___| /__/\__\__,_|\__|\_,_/__/
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn executable-exists [script label]
|
|
||||||
(if (not (len script))
|
|
||||||
(sys.exit (.format (_ "Syntax error in command configuration for {} ") label))
|
|
||||||
(let [[scriptname (get (.split script " ") 0)] [paths (.split (.get os.environ "PATH") ":")]
|
|
||||||
[isexecutable (fn [p] (and (os.path.exists p) (os.access p os.X_OK)))]]
|
|
||||||
(if (not (len scriptname))
|
|
||||||
(sys.exit (.format (_ "Syntax error in command configuration for {} ") label))
|
|
||||||
(if (= (get scriptname 0) "/")
|
|
||||||
(if (isexecutable scriptname)
|
|
||||||
scriptname None)
|
|
||||||
(let [[possibles (list (filter (fn [path] (isexecutable (os.path.join path scriptname))) paths))]]
|
|
||||||
(if (len possibles)
|
|
||||||
(get possibles 0) None)))))))
|
|
||||||
|
|
||||||
(defn get-working-linters [config]
|
|
||||||
(let [[found (fn [key] (executable-exists (.get (.get config key) "command") key))]]
|
|
||||||
(set (filter found (.keys config)))))
|
|
||||||
|
|
||||||
(defn print-linters [config]
|
|
||||||
(print (_ "Currently supported linters:"))
|
|
||||||
(let [[working (get-working-linters config)]
|
|
||||||
[broken (- (set (.keys config)) working)]]
|
|
||||||
(for [key (sorted working)]
|
|
||||||
(print (.format "{:<14} {}" key (.get (.get config key) "comment" ""))))
|
|
||||||
(for [key (sorted broken)]
|
|
||||||
(print (.format "{:<14} {}" key (_ "(WARNING: executable not found)"))))))
|
|
||||||
|
|
||||||
; ___ _ _ _ _ __ _ _ _
|
|
||||||
;| __(_) |___ _ __ __ _| |_| |_ / _(_) | |_ ___ _ _ ___
|
|
||||||
;| _|| | / -_) | '_ \/ _` | _| ' \ | _| | | _/ -_) '_(_-<
|
|
||||||
;|_| |_|_\___| | .__/\__,_|\__|_||_| |_| |_|_|\__\___|_| /__/
|
|
||||||
; |_|
|
|
||||||
|
|
||||||
(defn base-file-filter [files]
|
|
||||||
(map (fn [f] (os.path.join git-base f)) files))
|
|
||||||
|
|
||||||
(defn cwd-file-filter [files]
|
|
||||||
(let [[gitcwd (os.path.join (os.path.relpath (os.getcwd) git-base) "")]]
|
|
||||||
(base-file-filter (filter (fn [f] (.startswith f gitcwd)) files))))
|
|
||||||
|
|
||||||
(defn base-file-cleaner [files]
|
|
||||||
(map (fn [f] (.replace f git-base 1)) files))
|
|
||||||
|
|
||||||
; ___ __ _ _ _ _ _ _
|
|
||||||
;| _ \__ ___ __ __ / _(_) |___ | (_)__| |_ __ _ ___ _ _ ___ _ _ __ _| |_ ___ _ _ ___
|
|
||||||
;| / _` \ V V / | _| | / -_) | | (_-< _| / _` / -_) ' \/ -_) '_/ _` | _/ _ \ '_(_-<
|
|
||||||
;|_|_\__,_|\_/\_/ |_| |_|_\___| |_|_/__/\__| \__, \___|_||_\___|_| \__,_|\__\___/_| /__/
|
|
||||||
; |___/
|
|
||||||
|
|
||||||
(def *merge-conflict-pairs* (set ["DD" "DU" "AU" "AA" "UD" "UA" "UU"]))
|
|
||||||
(defn check-for-conflicts [files]
|
|
||||||
(let [[status-pairs (map (fn [(, index workspace filename)] (+ "" index workspace)) files)]
|
|
||||||
[conflicts (& (set *merge-conflict-pairs*) (set status-pairs))]]
|
|
||||||
(if (len conflicts)
|
|
||||||
(sys.exit (_ "Current repository contains merge conflicts. Linters will not be run."))
|
|
||||||
files)))
|
|
||||||
|
|
||||||
(defn remove-submodules [files]
|
|
||||||
(let [[split-out-paths (fn [s] (get (.split s " ") 2))]
|
|
||||||
[fixer-re (re.compile "^(\.\.\/)+")]
|
|
||||||
[fixer-to-base (fn [s] (.sub fixer-re "" s))]
|
|
||||||
[submodule-entries (split-git-response ["submodule" "status"])]
|
|
||||||
[submodule-names (map (fn [s] (fixer-to-base (split-out-paths s))) submodule-entries)]]
|
|
||||||
(filter (fn [s] (not (in s submodule-names))) files)))
|
|
||||||
|
|
||||||
(defn get-porcelain-status []
|
|
||||||
(let [[cmd ["status" "-z" "--porcelain" "--untracked-files=all" "--ignore-submodules=all"]]
|
|
||||||
[nonnull (fn [s] (> (len s) 0))]
|
|
||||||
[stream (list (filter nonnull (.split (get-git-response cmd) "\0")))]
|
|
||||||
[parse-stream (fn [acc stream]
|
|
||||||
(if (= 0 (len stream))
|
|
||||||
acc
|
|
||||||
(let [[temp (.pop stream 0)]
|
|
||||||
[index (get temp 0)]
|
|
||||||
[workspace (get temp 1)]
|
|
||||||
[filename (slice temp 3)]]
|
|
||||||
(if (= index "R")
|
|
||||||
(.pop stream 0))
|
|
||||||
(parse-stream (+ acc [(, index workspace filename)]) stream))))]]
|
|
||||||
(check-for-conflicts (parse-stream [] stream))))
|
|
||||||
|
|
||||||
(defn staging-list []
|
|
||||||
(map (fn [(, index workspace filename)] filename)
|
|
||||||
(filter (fn [(, index workspace filename)] (in index ["A" "M"])) (get-porcelain-status))))
|
|
||||||
|
|
||||||
(defn working-list []
|
|
||||||
(map (fn [(, index workspace filename)] filename)
|
|
||||||
(filter (fn [(, index workspace filename)] (in workspace ["A" "M" "?"])) (get-porcelain-status))))
|
|
||||||
|
|
||||||
(defn all-list []
|
|
||||||
(let [[cmd ["ls-tree" "--name-only" "--full-tree" "-r" "-z" git-head]]]
|
|
||||||
(filter (fn [s] (> (len s) 0)) (.split (get-git-response cmd) "\0"))))
|
|
||||||
|
|
||||||
; _ _ _ __ _ _ _ _ _ _
|
|
||||||
; /_\ ______ ___ _ __ | |__| |___ / _(_) |___ | (_)__| |_ __ _ ___ _ _ ___ _ _ __ _| |_ ___ _ _
|
|
||||||
; / _ \ (_-<_-</ -_) ' \| '_ \ / -_) | _| | / -_) | | (_-< _| / _` / -_) ' \/ -_) '_/ _` | _/ _ \ '_|
|
|
||||||
; /_/ \_\/__/__/\___|_|_|_|_.__/_\___| |_| |_|_\___| |_|_/__/\__| \__, \___|_||_\___|_| \__,_|\__\___/_|
|
|
||||||
; |___/
|
|
||||||
;
|
|
||||||
; Returns a list of all the files in the repository for a given strategy: staging or
|
|
||||||
; workspace, base, all, base + all. Halts if the repository is in an unstable (merging)
|
|
||||||
; state.
|
|
||||||
;
|
|
||||||
(defn get-filelist [options]
|
|
||||||
(let [[keys (.keys options)]
|
|
||||||
[working-directory-trans (if (len (& (set keys) (set ["base" "every"]))) base-file-filter cwd-file-filter)]
|
|
||||||
[file-list-generator (cond [(in "staging" keys) staging-list]
|
|
||||||
[(in "all" keys) all-list]
|
|
||||||
[true working-list])]]
|
|
||||||
(set ((fn [] (working-directory-trans (remove-submodules (file-list-generator))))))))
|
|
||||||
|
|
||||||
; ___ _
|
|
||||||
; / __| |_ ___ ___ ___ ___ __ _ _ _ _ _ _ _ _ _ ___ _ _
|
|
||||||
;| (__| ' \/ _ \/ _ (_-</ -_) / _` | | '_| || | ' \| ' \/ -_) '_|
|
|
||||||
; \___|_||_\___/\___/__/\___| \__,_| |_| \_,_|_||_|_||_\___|_|
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn staging-wrapper [run-linters]
|
|
||||||
(let [[time-gather (fn [f] (let [[stats (os.stat f)]]
|
|
||||||
(, f (, stats.atime stats.mtime))))]
|
|
||||||
[times (list (map time-gather files))]]
|
|
||||||
(run-git-command ["stash" "--keep-index"])
|
|
||||||
(let [[results (run-linters)]]
|
|
||||||
(run-git-command ["reset" "--hard"])
|
|
||||||
(run-git-command ["stash" "pop" "--quiet" "--index"])
|
|
||||||
(for [(, filename timepair) times]
|
|
||||||
(os.utime filename timepair))
|
|
||||||
results)))
|
|
||||||
|
|
||||||
(defn workspace-wrapper [run-linters]
|
|
||||||
(run-linters))
|
|
||||||
|
|
||||||
|
|
||||||
; Returns a function that takes the "main" program function as its argument, and runs
|
|
||||||
; "main" in either the stage or workspace. If it runs it in the stage, it gathers all the
|
|
||||||
; file utimes, and attempts to restore them after restaging.
|
|
||||||
|
|
||||||
(defn pick-runner [options]
|
|
||||||
(let [[keys (.keys options)]]
|
|
||||||
(if (in "staging" keys) staging-wrapper workspace-wrapper)))
|
|
||||||
|
|
||||||
|
|
||||||
; ___ _ _ _ _
|
|
||||||
; | __|_ _____ __ _ _| |_ ___ ___ _ _ ___ | (_)_ _| |_
|
|
||||||
; | _|\ \ / -_) _| || | _/ -_) / _ \ ' \/ -_) | | | ' \ _|
|
|
||||||
; |___/_\_\___\__|\_,_|\__\___| \___/_||_\___| |_|_|_||_\__|
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn lmap (pred iter) (list (map pred iter)))
|
|
||||||
(defn encode-shell-messages [prefix messages]
|
|
||||||
(lmap (fn [line] (.format "{}{}" prefix (.decode line "utf-8")))
|
|
||||||
(.splitlines messages)))
|
|
||||||
|
|
||||||
(defn run-external-linter [filename linter]
|
|
||||||
(let [[cmd (+ (get linter "command") "\"" filename "\"")]
|
|
||||||
[(, out err returncode) (get-shell-response cmd)]]
|
|
||||||
(if (or (and out (= (.get linter "condition" "error") "output"))
|
|
||||||
err
|
|
||||||
(not (= returncode 0)))
|
|
||||||
(let [[prefix (if (get linter "print")
|
|
||||||
(.format "\t{}:" filename)
|
|
||||||
"\t")]
|
|
||||||
[output (+ (encode-shell-messages prefix out)
|
|
||||||
(if err (encode-shell-messages prefix err) []))]]
|
|
||||||
[(or returncode 1) output])
|
|
||||||
[0 []])))
|
|
||||||
|
|
||||||
(defn run-one-linter [linter filenames]
|
|
||||||
(let [[match-filter (make-match-filter linter)]
|
|
||||||
[config (get (.values linter) 0)]
|
|
||||||
[files (set (filter match-filter filenames))]]
|
|
||||||
(list (map (fn [f] (run-external-linter f config)) files))))
|
|
||||||
|
|
||||||
(defn build-lint-runner [linters filenames]
|
|
||||||
(fn []
|
|
||||||
(let [[keys (sorted (.keys linters))]]
|
|
||||||
(map (fn [key] (run-one-linter {key (get linters key)} filenames)) keys))))
|
|
||||||
|
|
||||||
; __ __ _
|
|
||||||
;| \/ |__ _(_)_ _
|
|
||||||
;| |\/| / _` | | ' \
|
|
||||||
;|_| |_\__,_|_|_||_|
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn subset-config [config keys]
|
|
||||||
(let [[ret {}]]
|
|
||||||
(for [item (.items config)]
|
|
||||||
(if (in (get item 0) keys) (assoc ret (get item 0) (get item 1))))
|
|
||||||
ret))
|
|
||||||
|
|
||||||
(defn run-gitlint [options config extras]
|
|
||||||
(let [[all-files (get-filelist options)]
|
|
||||||
[runner (pick-runner options)]
|
|
||||||
[match-filter (make-match-filter config)]
|
|
||||||
[lintable-files (set (filter match-filter all-files))] ; Files for which a linter is defined.
|
|
||||||
[unlintables (- (set all-files) lintable-files)] ; Files for which no linter is defined
|
|
||||||
[working-linters (get-working-linters config)]
|
|
||||||
[broken-linters (- (set config) (set working-linters))]
|
|
||||||
[cant-lint-filter (make-match-filter (subset-config config broken-linters))]
|
|
||||||
[cant-lintable (set (filter cant-lint-filter lintable-files))]
|
|
||||||
[lint-runner (build-lint-runner (subset-config config working-linters) lintable-files)]
|
|
||||||
[results (runner lint-runner)]]
|
|
||||||
(print "No Linter Available:" (list unlintables))
|
|
||||||
(print "Linter Executable Not Found for:" (list cant-lintable))
|
|
||||||
(print (list results))))
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(let [[opts (hyopt optlist args "git lint"
|
|
||||||
"Copyright (c) 2008, 2016 Kenneth M. \"Elf\" Sternberg <elf.sternberg@gmail.com>"
|
|
||||||
"0.0.4")]]
|
|
||||||
(if (= git-base None)
|
|
||||||
(sys.exit (_ "Not currently in a git repository."))
|
|
||||||
(try
|
|
||||||
(let [[options (.get_options opts)]
|
|
||||||
[config (get-config options git-base)]]
|
|
||||||
(cond [(.has_key options "help") (opts.print-help)]
|
|
||||||
[(.has_key options "version") (opts.print-version)]
|
|
||||||
[(.has_key options "linters") (print-linters config)]
|
|
||||||
[true (run-gitlint options config opts.filenames)]))
|
|
||||||
(catch [err getopt.GetoptError]
|
|
||||||
(do
|
|
||||||
(opts.print-help)))))))
|
|
|
@ -1,33 +0,0 @@
|
||||||
; -*- mode: clojure -*-
|
|
||||||
(import sys os.path gettext ConfigParser)
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
|
|
||||||
(defn -find-config-file [options base]
|
|
||||||
(if (.has_key options "config")
|
|
||||||
(let [[config (get options "config")]
|
|
||||||
[configpath (os.path.abspath config)]]
|
|
||||||
(if (os.path.isfile configpath)
|
|
||||||
configpath
|
|
||||||
(sys.exit (.format (_ "Configuration file not found: {}\n") config))))
|
|
||||||
(let [[home (os.path.join (.get os.environ "HOME"))]
|
|
||||||
[possibles (, (os.path.join base ".git-lint")
|
|
||||||
(os.path.join base ".git-lint/config")
|
|
||||||
(os.path.join home ".git-lint")
|
|
||||||
(os.path.join home ".git-lint/config"))]
|
|
||||||
[matches (list (filter os.path.isfile possibles))]]
|
|
||||||
(if (len matches) (get matches 0) (sys.exit (_ "No configuration file found"))))))
|
|
||||||
|
|
||||||
(defn -load-config-file [path git-base]
|
|
||||||
(let [[configloader (.SafeConfigParser ConfigParser)]
|
|
||||||
[config {}]]
|
|
||||||
(.read configloader path)
|
|
||||||
(.set configloader "DEFAULT" "repdir" git-base)
|
|
||||||
(for [section (.sections configloader)]
|
|
||||||
(let [[pairs {}]]
|
|
||||||
(for [(, k v) (.items configloader section)]
|
|
||||||
(assoc pairs k v))
|
|
||||||
(assoc config section pairs)))
|
|
||||||
config))
|
|
||||||
|
|
||||||
(defn get-config [options base]
|
|
||||||
(-load-config-file (-find-config-file options base) base))
|
|
|
@ -1,77 +0,0 @@
|
||||||
; -*- mode: clojure -*-
|
|
||||||
; Given a set of command-line arguments, compare that to a mapped
|
|
||||||
; version of the optlist and return a canonicalized dictionary of all
|
|
||||||
; the arguments that have been set. For example "-c" and "--config"
|
|
||||||
; will both be mapped to "config".
|
|
||||||
|
|
||||||
; Given a prefix of one or two dashes and a position in the above
|
|
||||||
; array, creates a function to map either the short or long option
|
|
||||||
; to the option name.
|
|
||||||
|
|
||||||
(import os sys inspect getopt gettext)
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
|
|
||||||
(defn get-script-name []
|
|
||||||
(if (getattr sys "frozen" False)
|
|
||||||
(let [[(, path name) (os.path.split sys.executable)]]
|
|
||||||
(name))
|
|
||||||
(let [[prefix (.upper sys.exec_prefix)]
|
|
||||||
[names (filter (fn [a] (let [[fname (get a 1)]]
|
|
||||||
(not (or (.startswith fname "<") (.startswith (.upper fname) prefx))))
|
|
||||||
(.stack inspect)))]
|
|
||||||
[name (.pop names)]]
|
|
||||||
(name))))
|
|
||||||
|
|
||||||
(defn make-opt-assoc [prefix pos]
|
|
||||||
(fn [acc it] (assoc acc (+ prefix (get it pos)) (get it 1)) acc))
|
|
||||||
|
|
||||||
(defn make-options-rationalizer [optlist]
|
|
||||||
(let [[short-opt-assoc (make-opt-assoc "-" 0)]
|
|
||||||
[long-opt-assoc (make-opt-assoc "--" 1)]
|
|
||||||
[fullset (reduce (fn [acc i] (-> (short-opt-assoc acc i)
|
|
||||||
(long-opt-assoc i))) optlist {})]]
|
|
||||||
(fn [acc it] (do (assoc acc (get fullset (get it 0)) (get it 1)) acc))))
|
|
||||||
|
|
||||||
(defn remove-conflicted-options [optlist config]
|
|
||||||
(let [[keys (.keys config)]
|
|
||||||
[marked (filter (fn [opt] (in (get opt 1) keys)) optlist)]
|
|
||||||
[exclude (reduce (fn [memo opt] (+ memo (if (> (len opt) 4) (get opt 4) []))) marked [])]
|
|
||||||
[excluded (filter (fn [key] (in key exclude)) keys)]
|
|
||||||
[cleaned (reduce (fn [memo key]
|
|
||||||
(if (not (in key excluded)) (assoc memo key (get config key))) memo) keys {})]]
|
|
||||||
(, cleaned excluded)))
|
|
||||||
|
|
||||||
(defclass hyopt []
|
|
||||||
[[--init-- (fn [self optlist args &optional [name ""] [copyright ""] [version "0.0.1"]]
|
|
||||||
(let [[optstringsshort
|
|
||||||
(.join "" (map (fn [i] (+ (. i [0]) (cond [(. i [2]) ":"] [true ""]))) optlist))]
|
|
||||||
[optstringslong
|
|
||||||
(list (map (fn [i] (+ (. i [1]) (cond [(. i [2]) "="] [true ""]))) optlist))]
|
|
||||||
[(, opt arg)
|
|
||||||
(getopt.getopt (slice args 1) optstringsshort optstringslong)]
|
|
||||||
[rationalize-options (make-options-rationalizer optlist)]
|
|
||||||
[(, newoptions excluded) (remove-conflicted-options
|
|
||||||
optlist (reduce (fn [acc i] (rationalize-options acc i)) opt {}))]]
|
|
||||||
(setv self.optlist optlist)
|
|
||||||
(setv self.options newoptions)
|
|
||||||
(setv self.excluded excluded)
|
|
||||||
(setv self.filenames arg)
|
|
||||||
(setv self.name (if name name (get-script-name)))
|
|
||||||
(setv self.version version)
|
|
||||||
(setv self.copyright copyright))
|
|
||||||
None)]
|
|
||||||
|
|
||||||
[get-options (fn [self] self.options)]
|
|
||||||
[get-keys (fn [self] (set (.keys self.options)))]
|
|
||||||
[print-help (fn [self]
|
|
||||||
(print (.format (_ "Usage: {} [options] [filenames]") self.name))
|
|
||||||
(for [item self.optlist] (print (.format " -{:<1} --{:<12} {}" (get item 0) (get item 1) (get item 3))))
|
|
||||||
(sys.exit))]
|
|
||||||
|
|
||||||
[print-version (fn [self]
|
|
||||||
(print (.format "{}" self.name self.version))
|
|
||||||
(if self.copyright
|
|
||||||
(print self.copyright))
|
|
||||||
(sys.exit))]])
|
|
||||||
|
|
||||||
|
|
|
@ -1,189 +0,0 @@
|
||||||
#!/usr/bin/env hy
|
|
||||||
(import ConfigParser os subprocess operator re gettext sys getopt)
|
|
||||||
(.append sys.path "Users/ksternberg/build/git-lint/git_lint_src")
|
|
||||||
(import [git-lint-options [hyopt]])
|
|
||||||
(import [git-lint-config [get-config]])
|
|
||||||
|
|
||||||
(def _ gettext.gettext)
|
|
||||||
(def *version* "0.0.2")
|
|
||||||
|
|
||||||
(defn tap [a] (print "TAP:" a) a)
|
|
||||||
|
|
||||||
; short-name long-name takes-args description precludes
|
|
||||||
(def optlist [["o" "only" true (_ "A comma-separated list of only those linters to run") ["exclude"]]
|
|
||||||
["x" "exclude" true (_ "A comma-separated list of linters to skip") []]
|
|
||||||
["l" "linters" false (_ "Show the list of configured linters")]
|
|
||||||
["b" "base" false (_ "Scan from the base directory rather than the current working") []]
|
|
||||||
["a" "all" false (_ "Scan all files in the repository, not just those that have changed")]
|
|
||||||
["e" "every" false (_ "Short for -b -a: scan everything")]
|
|
||||||
["w" "workspace" false (_ "Scan the workspace") ["staging"]]
|
|
||||||
["s" "staging" false (_ "Scan the staging area (useful for pre-commit).") ["base" "all" "every" "workspace"]]
|
|
||||||
["g" "changes" false (_ "Report lint failures only for diff'd sections") ["complete"]]
|
|
||||||
["p" "complete" false (_ "Report lint failures over whole files") []]
|
|
||||||
["c" "config" true (_ "Path to config file") []]
|
|
||||||
["d" "dryrun" false (_ "Report what git-lint would do, but don't actually do anything.") []]
|
|
||||||
["q" "quiet" false (_ "Produce a short report of files that failed to pass.") []]
|
|
||||||
["h" "help" false (_ "This help message") []]
|
|
||||||
["v" "version" false (_"Version information") []]])
|
|
||||||
|
|
||||||
(defn get-git-response-raw [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]
|
|
||||||
[process (subprocess.Popen fullcmd
|
|
||||||
:universal-newlines True
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(defn get-git-response [cmd]
|
|
||||||
(let [[(, out error returncode) (get-git-response-raw cmd)]] out))
|
|
||||||
|
|
||||||
(defn split-git-response [cmd]
|
|
||||||
(let [[(, out error returncode) (get-git-response-raw cmd)]] (.splitlines out)))
|
|
||||||
|
|
||||||
(defn split-git-response [cmd]
|
|
||||||
(let [[(, out error returncode) (get-git-response-raw cmd)]] (.splitlines out)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn run-git-command [cmd]
|
|
||||||
(let [[fullcmd (+ ["git"] cmd)]]
|
|
||||||
(subprocess.call fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE)))
|
|
||||||
|
|
||||||
(defn get-shell-response [fullcmd]
|
|
||||||
(let [[process (subprocess.Popen fullcmd
|
|
||||||
:stdout subprocess.PIPE
|
|
||||||
:stderr subprocess.PIPE
|
|
||||||
:universal-newlines True
|
|
||||||
:shell True)]
|
|
||||||
[(, out err) (.communicate process)]]
|
|
||||||
(, out err process.returncode)))
|
|
||||||
|
|
||||||
(def git-base (let [[(, out error returncode)
|
|
||||||
(get-git-response-raw ["rev-parse" "--show-toplevel"])]]
|
|
||||||
(if (not (= returncode 0)) None (.rstrip out))))
|
|
||||||
|
|
||||||
(defn get-all-from-cwd []
|
|
||||||
(split-git-response ["ls-tree" "--name-only" "-r" "HEAD"]))
|
|
||||||
|
|
||||||
(defn get-all-from-base []
|
|
||||||
(split-git-response ["ls-tree" "--name-only" "-r" "--full-tree" "HEAD"]))
|
|
||||||
|
|
||||||
; Any of these indicate the tree is in a merge
|
|
||||||
; conflict state and the user has bigger problems.
|
|
||||||
(def *merge-conflict-pairs* (set ["DD" "DU" "AU" "AA" "UD" "UA" "UU"]))
|
|
||||||
(defn get-changed-from-source [trackings]
|
|
||||||
(let [[conflicts (filter (fn [t] (t[0:2] in *merge-conflict-pairs*)) trackings)]]
|
|
||||||
(if (len conflicts)
|
|
||||||
(sys.exit (_ "Current repository contains merge conflicts. Linters will not be run."))
|
|
||||||
trackings)))
|
|
||||||
|
|
||||||
(defn get-porcelain-status []
|
|
||||||
(let [[cmd ["status" "-z" "--porcelain" "--untracked-files=all" "--ignore-submodules=all"]]
|
|
||||||
[nonnull (fn [s] (> (len s) 0))]
|
|
||||||
[stream (tap (list (filter nonnull (.split (get-git-response cmd) "\0"))))]
|
|
||||||
[parse-stream (fn [acc stream]
|
|
||||||
(if (= 0 (len stream))
|
|
||||||
acc
|
|
||||||
(let [[temp (.pop stream 0)]
|
|
||||||
[index (get temp 0)]
|
|
||||||
[workspace (get temp 1)]
|
|
||||||
[filename (tap (slice temp 3))]]
|
|
||||||
(if (= index "R")
|
|
||||||
(.pop stream 0))
|
|
||||||
(parse-stream (+ acc [(, index workspace filename)]) stream))))]]
|
|
||||||
(parse-stream [] stream)))
|
|
||||||
|
|
||||||
(defn modified-in-workspace [s] (in s[0] ["M" "A" "?"]))
|
|
||||||
(defn modified-in-staging [s] (in s[1] ["M" "A"]))
|
|
||||||
(defn get-name [s] (s[2]))
|
|
||||||
|
|
||||||
;(defn get-changed-from-cwd []
|
|
||||||
; (->> (get-changed-from_source (split-git-response ["status" "--porcelain" "--untracked-files=all"]))
|
|
||||||
; (filter (fn [s] (s[0] in
|
|
||||||
;
|
|
||||||
; (map (fn [s]
|
|
||||||
; (filter (fn [s] (
|
|
||||||
;
|
|
||||||
|
|
||||||
(defn get-changed-from-base []
|
|
||||||
(get-changed-from_source (split-git-response ["status" "--porcelain" "--untracked-files=all" git-base])))
|
|
||||||
|
|
||||||
(defn get-staged-from-cwd []
|
|
||||||
())
|
|
||||||
|
|
||||||
(defn gen-staged-from-base []
|
|
||||||
())
|
|
||||||
|
|
||||||
|
|
||||||
(defn make-match-filter-matcher [extensions]
|
|
||||||
(->> (map (fn [s] (.split s ",")) extensions)
|
|
||||||
(reduce operator.add)
|
|
||||||
(map (fn [s] (.strip s)))
|
|
||||||
(set)
|
|
||||||
(filter (fn [s] (not (= 0 (len s)))))
|
|
||||||
(map (fn [s] (.sub re "^\." "" s)))
|
|
||||||
(.join "|")
|
|
||||||
((fn [s] (+ "\.(" s ")$")))
|
|
||||||
((fn [s] (re.compile s re.I)))))
|
|
||||||
|
|
||||||
(defn make-match-filter [config]
|
|
||||||
(let [[matcher (make-match-filter-matcher (map (fn [v] (.get v "match" "" ))
|
|
||||||
(.itervalues config)))]]
|
|
||||||
(fn [path] (.search matcher path))))
|
|
||||||
|
|
||||||
(defn executable-exists [script label]
|
|
||||||
(if (not (len script))
|
|
||||||
(sys.exit (.format (_ "Syntax error in command configuration for {} ") label))
|
|
||||||
(let [[scriptname (get (.split script " ") 0)]
|
|
||||||
[paths (.split (.get os.environ "PATH") ":")]
|
|
||||||
[isexecutable (fn [p] (and (os.path.exists p) (os.access p os.X_OK)))]]
|
|
||||||
(if (not (len scriptname))
|
|
||||||
(sys.exit (.format (_ "Syntax error in command configuration for {} ") label))
|
|
||||||
(if (= (get scriptname 0) "/")
|
|
||||||
(if (isexecutable scriptname)
|
|
||||||
scriptname None)
|
|
||||||
(let [[possibles (list (filter (fn [path] (isexecutable (os.path.join path scriptname))) paths))]]
|
|
||||||
(if (len possibles)
|
|
||||||
(get possibles 0) None)))))))
|
|
||||||
|
|
||||||
(defn print-linters [config]
|
|
||||||
(print (_ "Currently supported linters:"))
|
|
||||||
(for [(, linter items) (.iteritems config)]
|
|
||||||
(if (not (executable-exists (.get items "command" "") linter))
|
|
||||||
(print (.format "{:<14} {}" linter (_ "(WARNING: executable not found)")))
|
|
||||||
(print (.format "{:<14} {}" linter (.get items "comment" ""))))))
|
|
||||||
|
|
||||||
(defn git-lint-main [options]
|
|
||||||
(print git-base)
|
|
||||||
(print (os.path.abspath __file__))
|
|
||||||
(let [[config (get-config options git-base)]]
|
|
||||||
(print options)
|
|
||||||
(print config)
|
|
||||||
(print (make-match-filter config))
|
|
||||||
(print (get-porcelain-status))))
|
|
||||||
|
|
||||||
|
|
||||||
(defmain [&rest args]
|
|
||||||
(for [option optlist]
|
|
||||||
(print (.format "``-{}`` ``--{}``\n {}" (get option 0) (get option 1) (get option 3)))))
|
|
||||||
|
|
||||||
|
|
||||||
;(defmain [&rest args]
|
|
||||||
; (if (= git-base None)
|
|
||||||
; (sys.exit (_ "Not currently in a git repository."))
|
|
||||||
; (try
|
|
||||||
; (let [[opts (hyopt optlist args "git lint"
|
|
||||||
; "Copyright (c) 2008, 2016 Kenneth M. \"Elf\" Sternberg <elf.sternberg@gmail.com>"
|
|
||||||
; "0.0.4")]
|
|
||||||
; [options opts.options]
|
|
||||||
; [config (get-config options git-base)]]
|
|
||||||
; (cond [(.has_key options "help") (opts.print-help)]
|
|
||||||
; [(.has_key options "version") (opts.print-version)]
|
|
||||||
; [(.has_key options "linters") (print-linters config)]
|
|
||||||
; [true (git-lint-main options)]))
|
|
||||||
; (catch [err getopt.GetoptError]
|
|
||||||
; (print (str err))
|
|
||||||
; (print-help)))))
|
|
||||||
;
|
|
385
git_lint_src/n1
385
git_lint_src/n1
|
@ -1,385 +0,0 @@
|
||||||
from hy.core.language import filter, is_integer, map, reduce
|
|
||||||
import ConfigParser
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
import operator
|
|
||||||
import re
|
|
||||||
import gettext
|
|
||||||
import sys
|
|
||||||
import getopt
|
|
||||||
sys.path.append(u'Users/ksternberg/build/git-lint/git_lint_src')
|
|
||||||
from git_lint_options import hyopt
|
|
||||||
from git_lint_config import get_config
|
|
||||||
_ = gettext.gettext
|
|
||||||
VERSION = u'0.0.2'
|
|
||||||
|
|
||||||
def tap(a):
|
|
||||||
print(u'TAP:', a)
|
|
||||||
return a
|
|
||||||
optlist = [[u'o', u'only', True, _(u'A comma-separated list of only those linters to run'), [u'exclude']], [u'x', u'exclude', True, _(u'A comma-separated list of linters to skip'), []], [u'l', u'linters', False, _(u'Show the list of configured linters')], [u'b', u'base', False, _(u'Check all changed files in the repository, not just those in the current directory.'), []], [u'a', u'all', False, _(u'Scan all files in the repository, not just those that have changed.')], [u'e', u'every', False, _(u'Short for -b -a: scan everything')], [u'w', u'workspace', False, _(u'Scan the workspace'), [u'staging']], [u's', u'staging', False, _(u'Scan the staging area (useful for pre-commit).'), []], [u'g', u'changes', False, _(u"Report lint failures only for diff'd sections"), [u'complete']], [u'p', u'complete', False, _(u'Report lint failures for all files'), []], [u'c', u'config', True, _(u'Path to config file'), []], [u'h', u'help', False, _(u'This help message'), []], [u'v', u'version', False, _(u'Version information'), []]]
|
|
||||||
|
|
||||||
def get_git_response_raw(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_2():
|
|
||||||
fullcmd = ([u'git'] + cmd)
|
|
||||||
process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
(out, err) = process.communicate()
|
|
||||||
(out, err)
|
|
||||||
return (out, err, process.returncode)
|
|
||||||
return _hy_anon_fn_2()
|
|
||||||
|
|
||||||
def get_git_response(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_4():
|
|
||||||
(out, error, returncode) = get_git_response_raw(cmd)
|
|
||||||
(out, error, returncode)
|
|
||||||
return out
|
|
||||||
return _hy_anon_fn_4()
|
|
||||||
|
|
||||||
def split_git_response(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_6():
|
|
||||||
(out, error, returncode) = get_git_response_raw(cmd)
|
|
||||||
(out, error, returncode)
|
|
||||||
return out.splitlines()
|
|
||||||
return _hy_anon_fn_6()
|
|
||||||
|
|
||||||
def split_git_response(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_8():
|
|
||||||
(out, error, returncode) = get_git_response_raw(cmd)
|
|
||||||
(out, error, returncode)
|
|
||||||
return out.splitlines()
|
|
||||||
return _hy_anon_fn_8()
|
|
||||||
|
|
||||||
def run_git_command(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_10():
|
|
||||||
fullcmd = ([u'git'] + cmd)
|
|
||||||
return subprocess.call(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
return _hy_anon_fn_10()
|
|
||||||
|
|
||||||
def get_shell_response(fullcmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_12():
|
|
||||||
process = subprocess.Popen(fullcmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
|
|
||||||
(out, err) = process.communicate()
|
|
||||||
(out, err)
|
|
||||||
return (out, err, process.returncode)
|
|
||||||
return _hy_anon_fn_12()
|
|
||||||
|
|
||||||
def _hy_anon_fn_14():
|
|
||||||
(out, error, returncode) = get_git_response_raw([u'rev-parse', u'--show-toplevel'])
|
|
||||||
(out, error, returncode)
|
|
||||||
return (None if (not (returncode == 0L)) else out.rstrip())
|
|
||||||
git_base = _hy_anon_fn_14()
|
|
||||||
|
|
||||||
def get_all_from_cwd():
|
|
||||||
return split_git_response([u'ls-tree', u'--name-only', u'-r', u'HEAD'])
|
|
||||||
|
|
||||||
def get_all_from_base():
|
|
||||||
return split_git_response([u'ls-tree', u'--name-only', u'-r', u'--full-tree', u'HEAD'])
|
|
||||||
MERGE_CONFLICT_PAIRS = set([u'DD', u'DU', u'AU', u'AA', u'UD', u'UA', u'UU'])
|
|
||||||
|
|
||||||
def get_changed_from_source(trackings):
|
|
||||||
|
|
||||||
def _hy_anon_fn_18():
|
|
||||||
|
|
||||||
def _hy_anon_fn_17(t):
|
|
||||||
return t([0:2], in, MERGE_CONFLICT_PAIRS)
|
|
||||||
conflicts = filter(_hy_anon_fn_17, trackings)
|
|
||||||
return (sys.exit(_(u'Current repository contains merge conflicts. Linters will not be run.')) if len(conflicts) else trackings)
|
|
||||||
return _hy_anon_fn_18()
|
|
||||||
|
|
||||||
def get_porcelain_status(cmd):
|
|
||||||
|
|
||||||
def _hy_anon_fn_22():
|
|
||||||
stream = get_git_response(cmd).split(u'\x00')
|
|
||||||
|
|
||||||
def parse_stream(acc, stream):
|
|
||||||
if (0L == len(stream)):
|
|
||||||
_hy_anon_var_1 = acc
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _hy_anon_fn_20():
|
|
||||||
temp = stream.pop(0L)
|
|
||||||
index = temp.pop(0L)
|
|
||||||
workspace = temp.pop(0L)
|
|
||||||
filename = temp[1L:]
|
|
||||||
(stream.pop(0L) if (index == u'R') else None)
|
|
||||||
return parse_stream(acc.append((index, workspace, filename)), stream)
|
|
||||||
_hy_anon_var_1 = _hy_anon_fn_20()
|
|
||||||
return _hy_anon_var_1
|
|
||||||
return parse_stream([], stream)
|
|
||||||
return _hy_anon_fn_22()
|
|
||||||
|
|
||||||
def modified_in_workspace(s):
|
|
||||||
return s([0L], in, [u'M', u'A', u'?'])
|
|
||||||
|
|
||||||
def modified_in_staging(s):
|
|
||||||
return s([1L], in, [u'M', u'A', u'?'])
|
|
||||||
|
|
||||||
def get_name(s):
|
|
||||||
return s([2L])
|
|
||||||
|
|
||||||
def get_changed_from_base():
|
|
||||||
return get_changed_from_source(split_git_response([u'status', u'--porcelain', u'--untracked-files=all', git_base]))
|
|
||||||
|
|
||||||
def get_staged_from_cwd():
|
|
||||||
return []
|
|
||||||
|
|
||||||
def gen_staged_from_base():
|
|
||||||
return []
|
|
||||||
|
|
||||||
def make_match_filter_matcher(extensions):
|
|
||||||
|
|
||||||
def _hy_anon_fn_30(s):
|
|
||||||
return re.compile(s, re.I)
|
|
||||||
|
|
||||||
def _hy_anon_fn_31(s):
|
|
||||||
return ((u'\\.(' + s) + u')$')
|
|
||||||
|
|
||||||
def _hy_anon_fn_32(s):
|
|
||||||
return re.sub(u'^\\.', u'', s)
|
|
||||||
|
|
||||||
def _hy_anon_fn_33(s):
|
|
||||||
return (not (0L == len(s)))
|
|
||||||
|
|
||||||
def _hy_anon_fn_34(s):
|
|
||||||
return s.strip()
|
|
||||||
|
|
||||||
def _hy_anon_fn_35(s):
|
|
||||||
return s.split(u',')
|
|
||||||
return _hy_anon_fn_30(_hy_anon_fn_31(u'|'.join(map(_hy_anon_fn_32, filter(_hy_anon_fn_33, set(map(_hy_anon_fn_34, reduce(operator.add, map(_hy_anon_fn_35, extensions)))))))))
|
|
||||||
|
|
||||||
def make_match_filter(config):
|
|
||||||
|
|
||||||
def _hy_anon_fn_39():
|
|
||||||
|
|
||||||
def _hy_anon_fn_37(v):
|
|
||||||
return v.get(u'match', u'')
|
|
||||||
matcher = make_match_filter_matcher(map(_hy_anon_fn_37, config.itervalues()))
|
|
||||||
|
|
||||||
def _hy_anon_fn_38(path):
|
|
||||||
return matcher.search(path)
|
|
||||||
return _hy_anon_fn_38
|
|
||||||
return _hy_anon_fn_39()
|
|
||||||
|
|
||||||
def executable_exists(script, label):
|
|
||||||
if (not len(script)):
|
|
||||||
_hy_anon_var_4 = sys.exit(_(u'Syntax error in command configuration for {} ').format(label))
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _hy_anon_fn_44():
|
|
||||||
scriptname = script.split(u' ')[0L]
|
|
||||||
paths = os.environ.get(u'PATH').split(u':')
|
|
||||||
|
|
||||||
def isexecutable(p):
|
|
||||||
return (os.path.exists(p) and os.access(p, os.X_OK))
|
|
||||||
if (not len(scriptname)):
|
|
||||||
_hy_anon_var_3 = sys.exit(_(u'Syntax error in command configuration for {} ').format(label))
|
|
||||||
else:
|
|
||||||
if (scriptname[0L] == u'/'):
|
|
||||||
_hy_anon_var_2 = (scriptname if isexecutable(scriptname) else None)
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _hy_anon_fn_43():
|
|
||||||
|
|
||||||
def _hy_anon_fn_42(path):
|
|
||||||
return isexecutable(os.path.join(path, scriptname))
|
|
||||||
possibles = list(filter(_hy_anon_fn_42, paths))
|
|
||||||
return (possibles[0L] if len(possibles) else None)
|
|
||||||
_hy_anon_var_2 = _hy_anon_fn_43()
|
|
||||||
_hy_anon_var_3 = _hy_anon_var_2
|
|
||||||
return _hy_anon_var_3
|
|
||||||
_hy_anon_var_4 = _hy_anon_fn_44()
|
|
||||||
return _hy_anon_var_4
|
|
||||||
|
|
||||||
def print_linters(config):
|
|
||||||
print(_(u'Currently supported linters:'))
|
|
||||||
for (linter, items) in config.iteritems():
|
|
||||||
(print(u'{:<14} {}'.format(linter, _(u'(WARNING: executable not found)'))) if (not executable_exists(items.get(u'command', u''), linter)) else print(u'{:<14} {}'.format(linter, items.get(u'comment', u''))))
|
|
||||||
|
|
||||||
def get_porcelain_status():
|
|
||||||
|
|
||||||
def _hy_anon_fn_50():
|
|
||||||
cmd = [u'status', u'-z', u'--porcelain', u'--untracked-files=all', u'--ignore-submodules=all']
|
|
||||||
|
|
||||||
def nonnull(s):
|
|
||||||
return (len(s) > 0L)
|
|
||||||
stream = tap(list(filter(nonnull, get_git_response(cmd).split(u'\x00'))))
|
|
||||||
|
|
||||||
def parse_stream(acc, stream):
|
|
||||||
if (0L == len(stream)):
|
|
||||||
_hy_anon_var_5 = acc
|
|
||||||
else:
|
|
||||||
|
|
||||||
def _hy_anon_fn_48():
|
|
||||||
temp = stream.pop(0L)
|
|
||||||
index = temp[0L]
|
|
||||||
workspace = temp[1L]
|
|
||||||
filename = tap(temp[3L:])
|
|
||||||
(stream.pop(0L) if (index == u'R') else None)
|
|
||||||
return parse_stream((acc + [(index, workspace, filename)]), stream)
|
|
||||||
_hy_anon_var_5 = _hy_anon_fn_48()
|
|
||||||
return _hy_anon_var_5
|
|
||||||
return parse_stream([], stream)
|
|
||||||
return _hy_anon_fn_50()
|
|
||||||
|
|
||||||
def staging_wrapper(files, run_linters):
|
|
||||||
|
|
||||||
def _hy_anon_fn_55():
|
|
||||||
|
|
||||||
def time_gather(f):
|
|
||||||
|
|
||||||
def _hy_anon_fn_52():
|
|
||||||
stats = os.stat(f)
|
|
||||||
return (f, (stats.atime, stats.mtime))
|
|
||||||
return _hy_anon_fn_52()
|
|
||||||
times = list(map(time_gather, files))
|
|
||||||
run_git_command([u'stash', u'--keep-index'])
|
|
||||||
|
|
||||||
def _hy_anon_fn_54():
|
|
||||||
results = run_linters(files)
|
|
||||||
run_git_command([u'reset', u'--hard'])
|
|
||||||
run_git_command([u'stash', u'pop', u'--quiet', u'--index'])
|
|
||||||
for (filename, timepair) in times:
|
|
||||||
os.utime(filename, timepair)
|
|
||||||
return results
|
|
||||||
return _hy_anon_fn_54()
|
|
||||||
return _hy_anon_fn_55()
|
|
||||||
|
|
||||||
def workspace_wrapper(files, run_linters):
|
|
||||||
return run_linters(files)
|
|
||||||
|
|
||||||
def remove_submodules(files):
|
|
||||||
|
|
||||||
def _hy_anon_fn_62():
|
|
||||||
|
|
||||||
def split_out_paths(s):
|
|
||||||
return s.split(u' ')[2L]
|
|
||||||
fixer_re = re.compile(u'^(\\.\\.\\/)+')
|
|
||||||
|
|
||||||
def fixer_to_base(s):
|
|
||||||
return fixer_re.sub(u'', s)
|
|
||||||
submodule_entries = split_git_response([u'submodule', u'status'])
|
|
||||||
|
|
||||||
def _hy_anon_fn_60(s):
|
|
||||||
return fixer_to_base(split_out_paths(s))
|
|
||||||
submodule_names = map(_hy_anon_fn_60, submodule_entries)
|
|
||||||
|
|
||||||
def _hy_anon_fn_61(s):
|
|
||||||
return (not (s in submodule_names))
|
|
||||||
return filter(_hy_anon_fn_61, submodule_names)
|
|
||||||
return _hy_anon_fn_62()
|
|
||||||
|
|
||||||
def base_file_filter(files):
|
|
||||||
|
|
||||||
def _hy_anon_fn_64(f):
|
|
||||||
return os.path.join(git_base, f)
|
|
||||||
return map(_hy_anon_fn_64, files)
|
|
||||||
|
|
||||||
def cwd_file_filter(files):
|
|
||||||
|
|
||||||
def _hy_anon_fn_67():
|
|
||||||
gitcwd = os.path.join(os.path.relpath(os.getcwd(), git_base), u'')
|
|
||||||
|
|
||||||
def _hy_anon_fn_66(f):
|
|
||||||
return f.startswith(gitcwd)
|
|
||||||
return base_file_filter(filter(_hy_anon_fn_66, files))
|
|
||||||
return _hy_anon_fn_67()
|
|
||||||
|
|
||||||
def base_file_cleaner(files):
|
|
||||||
|
|
||||||
def _hy_anon_fn_69(f):
|
|
||||||
return f.replace(git_base, 1L)
|
|
||||||
return map(_hy_anon_fn_69, files)
|
|
||||||
|
|
||||||
def staging_list():
|
|
||||||
|
|
||||||
def _hy_anon_fn_71(_hy_anon_var_6):
|
|
||||||
(index, workspace, filename) = _hy_anon_var_6
|
|
||||||
(index, workspace, filename)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def _hy_anon_fn_72(_hy_anon_var_7):
|
|
||||||
(index, workspace, filename) = _hy_anon_var_7
|
|
||||||
(index, workspace, filename)
|
|
||||||
return (index in [u'A', u'M'])
|
|
||||||
return map(_hy_anon_fn_71, filter(_hy_anon_fn_72, get_porcelain_status))
|
|
||||||
|
|
||||||
def working_list():
|
|
||||||
|
|
||||||
def _hy_anon_fn_74(_hy_anon_var_8):
|
|
||||||
(index, workspace, filename) = _hy_anon_var_8
|
|
||||||
(index, workspace, filename)
|
|
||||||
return filename
|
|
||||||
|
|
||||||
def _hy_anon_fn_75(_hy_anon_var_9):
|
|
||||||
(index, workspace, filename) = _hy_anon_var_9
|
|
||||||
(index, workspace, filename)
|
|
||||||
return (working in [u'A', u'M', u'?'])
|
|
||||||
return map(_hy_anon_fn_74, filter(_hy_anon_fn_75, get_porcelain_status))
|
|
||||||
|
|
||||||
def all_list():
|
|
||||||
|
|
||||||
def _hy_anon_fn_78():
|
|
||||||
cmd = [u'ls-tree', u'--name-only', u'--full-tree', u'-r', git_head]
|
|
||||||
|
|
||||||
def _hy_anon_fn_77(s):
|
|
||||||
return (len(s) > 0L)
|
|
||||||
return filter(_hy_anon_fn_77, get_git_response(cmd).split(u'\x00'))
|
|
||||||
return _hy_anon_fn_78()
|
|
||||||
|
|
||||||
def pick_filelist_strategy(options):
|
|
||||||
|
|
||||||
def _hy_anon_fn_81():
|
|
||||||
keys = options.keys()
|
|
||||||
working_directory_trans = (base_file_filter if len((set(keys) & set([u'b', u'e']))) else cwd_file_filter)
|
|
||||||
file_list_generator = (staging_list if (u's' in keys) else (all_list if (u'a' in keys) else (working_list if True else None)))
|
|
||||||
|
|
||||||
def _hy_anon_fn_80():
|
|
||||||
return working_directory_trans(remove_submodules(file_list_generator()))
|
|
||||||
return _hy_anon_fn_80
|
|
||||||
return _hy_anon_fn_81()
|
|
||||||
|
|
||||||
def pick_runner(options):
|
|
||||||
|
|
||||||
def _hy_anon_fn_83():
|
|
||||||
keys = options.keys()
|
|
||||||
return (staging_wrapper if (u's' in keys) else workspace_wrapper)
|
|
||||||
return _hy_anon_fn_83()
|
|
||||||
|
|
||||||
def run_gitlint(options, config, extras):
|
|
||||||
|
|
||||||
def _hy_anon_fn_85():
|
|
||||||
file_lister = pick_filelist_strategy(options)
|
|
||||||
runner = pick_runner(options)
|
|
||||||
return print(file_lister())
|
|
||||||
return _hy_anon_fn_85()
|
|
||||||
|
|
||||||
def main(*args):
|
|
||||||
|
|
||||||
def _hy_anon_fn_88():
|
|
||||||
opts = hyopt(optlist, args, u'git lint', u'Copyright (c) 2008, 2016 Kenneth M. "Elf" Sternberg <elf.sternberg@gmail.com>', u'0.0.4')
|
|
||||||
if (git_base == None):
|
|
||||||
_hy_anon_var_11 = sys.exit(_(u'Not currently in a git repository.'))
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
|
|
||||||
def _hy_anon_fn_87():
|
|
||||||
options = opts.get_options()
|
|
||||||
config = get_config(options, git_base)
|
|
||||||
return (opts.print_help() if options.has_key(u'help') else (opts.print_version() if options.has_key(u'version') else (print_linters(config) if options.has_key(u'linters') else (run_gitlint(options, config, opts.filenames) if True else None))))
|
|
||||||
_hy_anon_var_10 = _hy_anon_fn_87()
|
|
||||||
except getopt.GetoptError as err:
|
|
||||||
_hy_anon_var_10 = opts.print_help()
|
|
||||||
_hy_anon_var_11 = _hy_anon_var_10
|
|
||||||
return _hy_anon_var_11
|
|
||||||
return _hy_anon_fn_88()
|
|
||||||
if (__name__ == u'__main__'):
|
|
||||||
import sys
|
|
||||||
:G_1235 = main(*sys.argv)
|
|
||||||
_hy_anon_var_12 = (sys.exit(:G_1235) if is_integer(:G_1235) else None)
|
|
||||||
else:
|
|
||||||
_hy_anon_var_12 = None
|
|
Loading…
Reference in New Issue