2016-05-30 13:51:12 +00:00
|
|
|
#!/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\\.(?P<type>IntSlice|StringSlice){(?P<args>[^}]*)}',
|
|
|
|
flags=re.DOTALL
|
|
|
|
)
|
2016-05-31 20:39:56 +00:00
|
|
|
_FLAG_LITERAL_RE = re.compile('(?P<prefix>\\s+)cli\\.(?P<type>\\w+)Flag{')
|
|
|
|
_COMMAND_SLICE_RE = re.compile('(?P<prefix>\\[\\])cli\\.Command{')
|
|
|
|
_COMMAND_LITERAL_RE = re.compile('(?P<prefix>\\s+)cli\\.Command{')
|
2016-05-30 19:15:49 +00:00
|
|
|
_EXIT_ERROR_RE = re.compile('cli\\.NewExitError')
|
2016-05-31 20:39:56 +00:00
|
|
|
_CONTEXT_ARGS_INDEX_RE = re.compile('\\.Args\\(\\)\\[(?P<index>\\d+)\\]')
|
|
|
|
_ENVVAR_STRING_RE = re.compile('EnvVar:(?P<ws>\\s+)"(?P<string>[^"]+)"')
|
2016-05-30 19:15:49 +00:00
|
|
|
_BOOL_T_FLAG_RE = re.compile(
|
|
|
|
'cli\\.BoolTFlag{(?P<args>[^}]*)}',
|
|
|
|
flags=re.DOTALL
|
|
|
|
)
|
2016-05-30 13:51:12 +00:00
|
|
|
|
|
|
|
_MIGRATORS = {}
|
|
|
|
|
|
|
|
|
|
|
|
def main(sysargs=sys.argv[:]):
|
|
|
|
parser = argparse.ArgumentParser(
|
|
|
|
description=_DESCRIPTION,
|
|
|
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
|
2016-05-30 14:46:00 +00:00
|
|
|
parser.add_argument('path', nargs='*',
|
2016-05-30 13:51:12 +00:00
|
|
|
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'
|
|
|
|
)
|
|
|
|
|
2016-05-30 14:46:00 +00:00
|
|
|
paths = args.path
|
|
|
|
if len(paths) == 0:
|
|
|
|
paths = ['.']
|
|
|
|
|
|
|
|
for filepath in _find_candidate_files(paths):
|
2016-05-30 13:51:12 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2016-05-30 14:21:13 +00:00
|
|
|
def _find_candidate_files(paths):
|
|
|
|
for path in paths:
|
|
|
|
if not os.path.isdir(path):
|
|
|
|
yield path
|
|
|
|
continue
|
2016-05-30 13:51:12 +00:00
|
|
|
|
2016-05-30 14:21:13 +00:00
|
|
|
for curdir, dirs, files in os.walk(path):
|
|
|
|
for i, dirname in enumerate(dirs[:]):
|
|
|
|
if dirname.startswith('.'):
|
|
|
|
dirs.pop(i)
|
2016-05-30 13:51:12 +00:00
|
|
|
|
2016-05-30 14:21:13 +00:00
|
|
|
for filename in files:
|
2016-05-30 14:46:00 +00:00
|
|
|
if not filename.decode('utf-8').endswith('.go'):
|
2016-05-30 14:21:13 +00:00
|
|
|
continue
|
2016-05-30 13:51:12 +00:00
|
|
|
|
2016-05-30 14:21:13 +00:00
|
|
|
filepath = os.path.join(curdir, filename)
|
|
|
|
if not os.access(filepath, os.R_OK | os.W_OK):
|
|
|
|
continue
|
|
|
|
|
|
|
|
yield filepath
|
2016-05-30 13:51:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2016-05-30 19:15:49 +00:00
|
|
|
@_migrator
|
|
|
|
def _migrate_new_exit_error(source):
|
|
|
|
return _EXIT_ERROR_RE.sub('cli.Exit', source)
|
|
|
|
|
2016-05-30 19:24:16 +00:00
|
|
|
|
2016-05-30 19:15:49 +00:00
|
|
|
@_migrator
|
|
|
|
def _migrate_bool_t_flag(source):
|
|
|
|
return _BOOL_T_FLAG_RE.sub(_bool_t_flag_repl, source)
|
|
|
|
|
|
|
|
|
|
|
|
def _bool_t_flag_repl(match):
|
2016-05-30 19:24:16 +00:00
|
|
|
return 'cli.BoolFlag{{Value: true,{}}}'.format(
|
2016-05-30 19:15:49 +00:00
|
|
|
match.groupdict()['args']
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2016-05-31 20:39:56 +00:00
|
|
|
@_migrator
|
|
|
|
def _migrate_context_args_index(source):
|
|
|
|
return _CONTEXT_ARGS_INDEX_RE.sub(_context_args_index_repl, source)
|
|
|
|
|
|
|
|
|
|
|
|
def _context_args_index_repl(match):
|
|
|
|
return '.Args().Get({})'.format(match.groupdict()['index'])
|
|
|
|
|
|
|
|
|
|
|
|
@_migrator
|
|
|
|
def _migrate_envvar_string(source):
|
|
|
|
return _ENVVAR_STRING_RE.sub(_envvar_string_repl, source)
|
|
|
|
|
|
|
|
|
|
|
|
def _envvar_string_repl(match):
|
|
|
|
return 'EnvVars:{}[]string{{{}}}'.format(
|
|
|
|
match.groupdict()['ws'],
|
|
|
|
', '.join([
|
|
|
|
'"{}"'.format(s) for s in
|
|
|
|
re.split('\\s*,\\s*', match.groupdict()['string'])
|
|
|
|
])
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2016-05-30 13:51:12 +00:00
|
|
|
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"
|
2016-05-30 19:15:49 +00:00
|
|
|
"""),
|
|
|
|
("""
|
|
|
|
\t\t\t\treturn cli.NewExitError("foo whatebber", 9)
|
|
|
|
""", """
|
|
|
|
\t\t\t\treturn cli.Exit("foo whatebber", 9)
|
|
|
|
"""),
|
|
|
|
("""
|
|
|
|
\t\t\tapp.Flags = []cli.Flag{
|
|
|
|
\t\t\t\tcli.StringFlag{
|
|
|
|
\t\t\t\t\tName: "aha",
|
|
|
|
\t\t\t\t},
|
|
|
|
\t\t\t\tcli.BoolTFlag{
|
|
|
|
\t\t\t\t\tName: "blurp",
|
|
|
|
\t\t\t\t},
|
|
|
|
\t\t\t}
|
|
|
|
""", """
|
|
|
|
\t\t\tapp.Flags = []cli.Flag{
|
|
|
|
\t\t\t\t&cli.StringFlag{
|
|
|
|
\t\t\t\t\tName: "aha",
|
|
|
|
\t\t\t\t},
|
2016-05-30 19:24:16 +00:00
|
|
|
\t\t\t\t&cli.BoolFlag{Value: true,
|
2016-05-30 19:15:49 +00:00
|
|
|
\t\t\t\t\tName: "blurp",
|
|
|
|
\t\t\t\t},
|
|
|
|
\t\t\t}
|
2016-05-31 20:39:56 +00:00
|
|
|
"""),
|
|
|
|
("""
|
|
|
|
\t\t\tAction = func(c *cli.Context) error {
|
|
|
|
\t\t\t\tif c.Args()[4] == "meep" {
|
|
|
|
\t\t\t\t\treturn nil
|
|
|
|
\t\t\t\t}
|
|
|
|
\t\t\t\treturn errors.New("mope")
|
|
|
|
\t\t\t}
|
|
|
|
""", """
|
|
|
|
\t\t\tAction = func(c *cli.Context) error {
|
|
|
|
\t\t\t\tif c.Args().Get(4) == "meep" {
|
|
|
|
\t\t\t\t\treturn nil
|
|
|
|
\t\t\t\t}
|
|
|
|
\t\t\t\treturn errors.New("mope")
|
|
|
|
\t\t\t}
|
|
|
|
"""),
|
|
|
|
("""
|
|
|
|
\t\tapp.Flags = []cli.Flag{
|
|
|
|
\t\t\tcli.StringFlag{
|
|
|
|
\t\t\t\tName: "toots",
|
|
|
|
\t\t\t\tEnvVar: "TOOTS,TOOTERS",
|
|
|
|
\t\t\t},
|
|
|
|
\t\t}
|
|
|
|
""", """
|
|
|
|
\t\tapp.Flags = []cli.Flag{
|
|
|
|
\t\t\t&cli.StringFlag{
|
|
|
|
\t\t\t\tName: "toots",
|
|
|
|
\t\t\t\tEnvVars: []string{"TOOTS", "TOOTERS"},
|
|
|
|
\t\t\t},
|
|
|
|
\t\t}
|
2016-05-30 13:53:47 +00:00
|
|
|
""")
|
2016-05-30 13:51:12 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
sys.exit(main())
|