diff --git a/.travis.yml b/.travis.yml index 657e96a..66643e7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ language: go sudo: false +cache: pip + go: - 1.2.2 - 1.3.3 @@ -25,8 +27,11 @@ matrix: before_script: - go get github.com/urfave/gfmxr/... +- pip install flake8 script: +- flake8 runtests cli-v1-to-v2 - ./runtests vet - ./runtests test - ./runtests gfmxr +- ./cli-v1-to-v2 --selftest diff --git a/appveyor.yml b/appveyor.yml index 173086e..5714f8b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -18,8 +18,10 @@ install: - go env - go get github.com/urfave/gfmxr/... - go get -v -t ./... +- pip install flake8 build_script: +- flake8 runtests cli-v1-to-v2 - python runtests vet - python runtests test - python runtests gfmxr diff --git a/cli-migrate-slice-types b/cli-migrate-slice-types deleted file mode 100755 index 5c6cb1f..0000000 --- a/cli-migrate-slice-types +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function, unicode_literals - -import argparse -import os -import re -import sys - - -DESCRIPTION = """\ -Migrate arbitrary `.go` sources from the pre-v2.0.0 API for StringSlice and -IntSlice types, optionally writing the changes back to file. -""" -SLICE_TYPE_RE = re.compile( - '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', - flags=re.DOTALL -) - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser( - description=DESCRIPTION, - formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument('basedir', nargs='?', metavar='BASEDIR', - type=os.path.abspath, default=os.getcwd()) - parser.add_argument('-w', '--write', help='write changes back to file', - action='store_true', default=False) - - args = parser.parse_args(sysargs[1:]) - - for filepath in _find_candidate_files(args.basedir): - updated_source = _update_source(filepath) - if args.write: - print('Updating {!r}'.format(filepath)) - - with open(filepath, 'w') as outfile: - outfile.write(updated_source) - else: - print('// -> Updated {!r}'.format(filepath)) - print(updated_source) - - return 0 - - -def _update_source(filepath): - with open(filepath) as infile: - source = infile.read() - return SLICE_TYPE_RE.sub(_slice_type_repl, source) - - -def _slice_type_repl(match): - return 'cli.New{}({})'.format( - match.groupdict()['type'], match.groupdict()['args'] - ) - - -def _find_candidate_files(basedir): - for curdir, dirs, files in os.walk(basedir): - for i, dirname in enumerate(dirs[:]): - if dirname.startswith('.'): - dirs.pop(i) - - for filename in files: - if not filename.endswith('.go'): - continue - - filepath = os.path.join(curdir, filename) - if not os.access(filepath, os.R_OK | os.W_OK): - continue - - yield filepath - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/cli-v1-to-v2 b/cli-v1-to-v2 new file mode 100755 index 0000000..8743245 --- /dev/null +++ b/cli-v1-to-v2 @@ -0,0 +1,235 @@ +#!/usr/bin/env python +from __future__ import print_function, unicode_literals + +import argparse +import logging +import os +import re +import sys + + +_DESCRIPTION = """\ +Migrate arbitrary `.go` sources (mostly) from the v1 to v2 API. +""" +_V2_IMPORT = '"gopkg.in/urfave/cli.v2"' +_V1_IMPORT_RE = re.compile( + '"(?:github\\.com|gopkg\\.in)/(?:codegangsta|urfave)/cli(?:\\.v1|)"', +) +_SLICE_TYPE_RE = re.compile( + '&cli\\.(?PIntSlice|StringSlice){(?P[^}]*)}', + flags=re.DOTALL +) +_FLAG_LITERAL_RE = re.compile( + '(?P[^&]?)cli\\.(?P\\w+)Flag{', +) +_COMMAND_SLICE_RE = re.compile( + '(?P\\[\\])cli\\.Command{', +) +_COMMAND_LITERAL_RE = re.compile( + '(?P\\s+)cli\\.Command{', +) + +_MIGRATORS = {} + + +def main(sysargs=sys.argv[:]): + parser = argparse.ArgumentParser( + description=_DESCRIPTION, + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('basedir', nargs='?', metavar='BASEDIR', + type=os.path.abspath, default=os.getcwd()) + parser.add_argument('-w', '--write', help='write changes back to file', + action='store_true', default=False) + parser.add_argument('-q', '--quiet', help='quiet down the logging', + action='store_true', default=False) + parser.add_argument('--selftest', help='run internal tests', + action='store_true', default=False) + + args = parser.parse_args(sysargs[1:]) + + if args.selftest: + logging.basicConfig( + level=logging.WARN, + format='selftest: %(message)s' + ) + test_migrators() + return 0 + + logging.basicConfig( + level=(logging.FATAL if args.quiet else logging.INFO), + format='%(message)s' + ) + + for filepath in _find_candidate_files(args.basedir): + updated_source = _update_filepath(filepath) + if args.write: + logging.info('Updating %s', filepath) + + with open(filepath, 'w') as outfile: + outfile.write(updated_source) + else: + logging.info('// Updated %s:', filepath) + print(updated_source) + + return 0 + + +def _find_candidate_files(basedir): + for curdir, dirs, files in os.walk(basedir): + for i, dirname in enumerate(dirs[:]): + if dirname.startswith('.'): + dirs.pop(i) + + for filename in files: + if not filename.endswith('.go'): + continue + + filepath = os.path.join(curdir, filename) + if not os.access(filepath, os.R_OK | os.W_OK): + continue + + yield filepath + + +def _update_filepath(filepath): + with open(filepath) as infile: + return _update_source(infile.read()) + + +def _update_source(source): + for migrator, func in _MIGRATORS.items(): + logging.info('Running %s migrator', migrator) + source = func(source) + return source + + +def _migrator(func): + _MIGRATORS[func.__name__.replace('_migrate_', '')] = func + return func + + +@_migrator +def _migrate_command_slice(source): + return _COMMAND_SLICE_RE.sub(_command_slice_repl, source) + + +def _command_slice_repl(match): + return '{}*cli.Command{{'.format(match.groupdict()['prefix']) + + +@_migrator +def _migrate_command_literal(source): + return _COMMAND_LITERAL_RE.sub(_command_literal_repl, source) + + +def _command_literal_repl(match): + return '{}&cli.Command{{'.format(match.groupdict()['prefix']) + + +@_migrator +def _migrate_slice_types(source): + return _SLICE_TYPE_RE.sub(_slice_type_repl, source) + + +def _slice_type_repl(match): + return 'cli.New{}({})'.format( + match.groupdict()['type'], match.groupdict()['args'] + ) + + +@_migrator +def _migrate_flag_literals(source): + return _FLAG_LITERAL_RE.sub(_flag_literal_repl, source) + + +def _flag_literal_repl(match): + return '{}&cli.{}Flag{{'.format( + match.groupdict()['prefix'], match.groupdict()['type'] + ) + + +@_migrator +def _migrate_v1_imports(source): + return _V1_IMPORT_RE.sub(_V2_IMPORT, source) + + +def test_migrators(): + import difflib + + for i, (source, expected) in enumerate(_MIGRATOR_TESTS): + actual = _update_source(source) + if expected != actual: + udiff = difflib.unified_diff( + expected.splitlines(), actual.splitlines(), + fromfile='a/source.go', tofile='b/source.go', lineterm='' + ) + for line in udiff: + print(line) + raise AssertionError('migrated source does not match expected') + logging.warn('Test case %d/%d OK', i+1, len(_MIGRATOR_TESTS)) + + +_MIGRATOR_TESTS = ( + (""" +\t\t\t&cli.StringSlice{"a", "b", "c"}, +""", """ +\t\t\tcli.NewStringSlice("a", "b", "c"), +"""), + (""" +\t\tcli.IntFlag{ +\t\t\tName: "yep", +\t\t\tValue: 3, +\t\t} +""", """ +\t\t&cli.IntFlag{ +\t\t\tName: "yep", +\t\t\tValue: 3, +\t\t} +"""), + (""" +\t\tapp.Commands = []cli.Command{ +\t\t\t{ +\t\t\t\tName: "whatebbs", +\t\t\t}, +\t\t} +""", """ +\t\tapp.Commands = []*cli.Command{ +\t\t\t{ +\t\t\t\tName: "whatebbs", +\t\t\t}, +\t\t} +"""), + (""" +\t\tapp.Commands = []cli.Command{ +\t\t\tcli.Command{ +\t\t\t\tName: "whatebbs", +\t\t\t}, +\t\t} +""", """ +\t\tapp.Commands = []*cli.Command{ +\t\t\t&cli.Command{ +\t\t\t\tName: "whatebbs", +\t\t\t}, +\t\t} +"""), + (""" +\t"github.com/codegangsta/cli" +\t"github.com/urfave/cli" +\t"gopkg.in/codegangsta/cli" +\t"gopkg.in/codegangsta/cli.v1" +\t"gopkg.in/urfave/cli" +\t"gopkg.in/urfave/cli.v1" +""", """ +\t"gopkg.in/urfave/cli.v2" +\t"gopkg.in/urfave/cli.v2" +\t"gopkg.in/urfave/cli.v2" +\t"gopkg.in/urfave/cli.v2" +\t"gopkg.in/urfave/cli.v2" +\t"gopkg.in/urfave/cli.v2" +""" + ) +) + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/runtests b/runtests index 72c1f0d..d077055 100755 --- a/runtests +++ b/runtests @@ -9,18 +9,13 @@ import tempfile from subprocess import check_call, check_output -PACKAGE_NAME = os.environ.get( +_PACKAGE_NAME = os.environ.get( 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' ) +_TARGETS = {} def main(sysargs=sys.argv[:]): - targets = { - 'vet': _vet, - 'test': _test, - 'gfmxr': _gfmxr - } - parser = argparse.ArgumentParser() parser.add_argument( 'target', nargs='?', choices=tuple(targets.keys()), default='test' @@ -31,6 +26,12 @@ def main(sysargs=sys.argv[:]): return 0 +def _target(func): + _TARGETS[func.__name__.strip('_')] = func + return func + + +@_target def _test(): if check_output('go version'.split()).split()[2] < 'go1.2': _run('go test -v .'.split()) @@ -46,7 +47,7 @@ def _test(): _run('go test -v'.split() + [ '-coverprofile={}'.format(coverprofile), - ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') + ('{}/{}'.format(_PACKAGE_NAME, subpackage)).rstrip('/') ]) combined_name = _combine_coverprofiles(coverprofiles) @@ -54,14 +55,24 @@ def _test(): os.remove(combined_name) +@_target def _gfmxr(): _run(['gfmxr', '-c', str(_gfmxr_count()), '-s', 'README.md']) +@_target def _vet(): _run('go vet ./...'.split()) +@_target +def _migrator(): + exe = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'cli-v1-to-v2' + ) + run(['py.test', exe]) + + def _run(command): print('runtests: {}'.format(' '.join(command)), file=sys.stderr) check_call(command)