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:
Elf M. Sternberg 2016-09-23 17:50:04 -07:00
parent 56037fe109
commit f2873bc07c
12 changed files with 330 additions and 1746 deletions

View File

@ -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

View File

@ -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,204 +172,271 @@ def print_linters(config):
print('{:<14} {}'.format(key, _('(WARNING: executable not found)'))) print('{:<14} {}'.format(key, _('(WARNING: executable not found)')))
def base_file_filter(files):
return [os.path.join(git_base, file) for file in files]
def cwd_file_filter(files):
gitcwd = os.path.join(os.path.relpath(os.getcwd(), git_base), '')
return base_file_filter([file for file in files
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):
status_pairs = set(['' + f[0] + f[1] for f in files])
if len(status_pairs & MERGE_CONFLICT_PAIRS):
sys.exit(_('Current repository contains merge conflicts. Linters will not be run.'))
return filesets
def remove_submodules(files):
fixer_re = re.compile('^(\\.\\.\\/)+')
submodules = split_git_response(['submodule', 'status'])
submodule_names = [fixer_re.sub('', submodule.split(' ')[2]) for submodule in submodules]
return [file for file in files if (file not in submodule_names)]
def get_porcelain_status():
cmd = [u'status', u'-z', u'--porcelain', u'--untracked-files=all', u'--ignore-submodules=all']
stream = [entry for entry in get_git_response(cmd).split(u'\x00')
if len(entry) > 0]
acc = []
while len(stream) > 0:
entry = stream.pop(0)
(index, workspace, filename) = (entry[0], entry[1], entry[3:])
if index == 'R':
stream.pop(0)
acc = acc + [(index, workspace, filename)]
return acc
def staging_list():
return [filename for (index, workspace, filename) in get_porcelain_status()
if index in ['A', 'M']]
def working_list():
return [filename for (index, workspace, filename) in get_porcelain_status()
if workspace in ['A', 'M', '?']]
def all_list():
cmd = ['ls-tree', '--name-only', '--full-tree', '-r', '-z', git_head]
return [file for file in get_git_response(cmd).split(u'\x00')
if len(file) > 0]
# ___ _ _ _ _ __ __ _ _
# / __|___| |_ | (_)__| |_ ___ / _| / _(_) |___ ___
# | (_ / -_) _| | | (_-< _| / _ \ _| | _| | / -_|_-<
# \___\___|\__| |_|_/__/\__| \___/_| |_| |_|_\___/__/
#
def get_filelist(options): def get_filelist(options):
keys = options.keys() """ Returns the list of files against which we'll run the linters. """
def base_file_filter(files):
""" Return the full path for all files """
return [os.path.join(git_base, file) for file in 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), '')
return base_file_filter([file for file in files
if file.startswith(gitcwd)])
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])
if len(status_pairs & MERGE_CONFLICT_PAIRS):
sys.exit(
_('Current repository contains merge conflicts. Linters will not be run.'))
return filesets
def remove_submodules(files):
""" Remove all submodules from the list of files git-lint cares about. """
fixer_re = re.compile('^(\\.\\.\\/)+')
submodules = split_git_response(['submodule', 'status'])
submodule_names = [fixer_re.sub('', submodule.split(' ')[2])
for submodule in submodules]
return [file for file in files if (file not in submodule_names)]
def get_porcelain_status():
""" 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')
if len(entry) > 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)
(index, workspace, filename) = (entry[0], entry[1], entry[3:])
if index == 'R':
stream.pop(0)
return parse_stream(acc + [(index, workspace, filename)], stream)
return check_for_conflicts(parse_stream([], stream))
def staging_list():
""" Return the list of files added or modified to the stage """
return [filename for (index, workspace, filename) in get_porcelain_status()
if index in ['A', 'M']]
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()
if workspace in ['A', 'M', '?']]
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]
return [file for file in get_git_response(cmd).split(u'\x00')
if len(file) > 0]
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))
def staging_wrapper(run_linters): # ___ _ _
# / __| |_ __ _ __ _(_)_ _ __ _ __ __ ___ _ __ _ _ __ _ __ ___ _ _
# \__ \ _/ _` / _` | | ' \/ _` | \ 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 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)
return results return results
def workspace_wrapper(run_linters):
return run_linters()
def workspace_wrapper(run_linters): return staging in options.keys() and staging_wrapper or workspace_wrapper
return run_linters()
def pick_runner(options):
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
keys = sorted(linters.keys()) runner to better handle stashing and restoring a staged commit.
"""
def lint_runner():
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):
all_files = get_filelist(options) """ Returns a subset of the configuration, with only those linters mentioned in keys """
runner = pick_runner(options) return {item[0]: item[1] for item in config.items() if item[0] in keys}
match_filter = make_match_filter(config)
lintable_files = set(filter(match_filter, all_files)) """ Runs the requested linters """
unlintables = (set(all_files) - lintable_files) all_files = get_filelist(options)
working_linters = get_working_linters(config) runner = pick_runner(options)
broken_linters = (set(config) - set(working_linters)) lintable = make_match_filter(config)
cant_lint_filter = make_match_filter(subset_config(config, broken_linters)) lintable_files = set([file for file in all_files if lintable(file)])
cant_lintable = set(filter(cant_lint_filter, lintable_files)) unlintables = (set(all_files) - lintable_files)
lint_runner = build_lint_runner(subset_config(config, working_linters), lintable_files) working_linters = get_working_linters(config)
results = runner(lint_runner) broken_linters = (set(config) - set(working_linters))
print(u'No Linter Available:', list(unlintables)) cant_lint = make_match_filter(subset_config(config, broken_linters))
print(u'Linter Executable Not Found for:', list(cant_lintable)) cant_lintable = set([file for file in lintable_files if cant_lint(file)])
return print(list(results)) lint_runner = build_lint_runner(build_config_subset(working_linters), lintable_files)
return _hy_anon_fn_94() results = runner(lint_runner)
print(list(results))
return results
def main(*args): def main(*args):
if git_base is None:
sys.exit(_('Not currently in a git repository.'))
def _hy_anon_fn_97(): opts = RationalOptions(
opts = hyopt(optlist, args, u'git lint', u'Copyright (c) 2008, 2016 Kenneth M. "Elf" Sternberg <elf.sternberg@gmail.com>', u'0.0.4') optlist, args,
if (git_base == None): 'git lint',
_hy_anon_var_14 = sys.exit(_(u'Not currently in a git repository.')) 'Copyright (c) 2008, 2016 Kenneth M. "Elf" Sternberg <elf.sternberg@gmail.com>',
else: '0.0.4')
try:
def _hy_anon_fn_96(): try:
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()
except getopt.GetoptError as err: return 0
_hy_anon_var_13 = opts.print_help()
_hy_anon_var_14 = _hy_anon_var_13 if 'version' in options:
return _hy_anon_var_14 opts.print_version()
return _hy_anon_fn_97() return 0
if (__name__ == u'__main__'):
if 'linters' in options:
opts.print_linters()
return 0
return run_gitlint(options, config, opts.filenames)
except getopt.GetoptError as err:
opts.print_help()
return 0
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

View File

@ -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()}

View File

@ -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):

View File

@ -1 +0,0 @@
# foo!

View File

@ -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")))

View File

@ -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))))

View File

@ -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)))))))

View File

@ -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))

View File

@ -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))]])

View File

@ -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)))))
;

View File

@ -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