コード例 #1
0
    def default_parser(
        self,
        parents=[],
        # input_pcaps: List[Tuple[str, PreprocessingActions]],
        **kwargs
    ) -> argparse_completer.ACArgumentParser:
        """
        Generates a parser with common options.
        This parser can be completed or overridden by its children.

        Args:
            mptcpstream: to accept an mptcp.stream id
            available_dataframe: True if a pcap was preloaded at start
            direction: Enable filtering the stream depending if the packets
            were sent towards the MPTCP client or the MPTCP server
            skip_subflows: Allow to hide some subflows from the plot

        Return:
            An argparse.ArgumentParser

        """
        parser = argparse_completer.ACArgumentParser(
            parents=parents,
            add_help=(parents == []),
        )

        parser.add_argument('-o',
                            '--out',
                            action="store",
                            default=None,
                            help='Name of the output plot')
        parser.add_argument(
            '--display',
            action="store_true",
            help='will display the generated plot (use xdg-open by default)')
        parser.add_argument('--title',
                            action="store",
                            type=str,
                            help='Overrides the default plot title')
        parser.add_argument(
            '--primary',
            action="store_true",
            help="Copy to X clipboard, requires `xsel` to be installed")
        return parser
コード例 #2
0
    def default_parser(self, *args, **kwargs):
        parser = argparse_completer.ACArgumentParser(
            description="Plot One Way Delays")

        subparsers = parser.add_subparsers(
            dest="protocol",
            title="Subparsers",
            help='sub-command help',
        )
        subparsers.required = True  # type: ignore

        actions = {
            "tcp":
            PreprocessingActions.MergeTcp
            | PreprocessingActions.FilterDestination,
            "mptcp":
            PreprocessingActions.MergeMpTcp
            | PreprocessingActions.FilterDestination,
        }

        for protocol, actions in actions.items():

            expected_pcaps = {"pcap": actions}

            temp = gen_pcap_parser(input_pcaps=expected_pcaps,
                                   parents=[super().default_parser()])
            subparser = subparsers.add_parser(protocol,
                                              parents=[
                                                  temp,
                                              ],
                                              add_help=False)

        parser.description = '''
            Helps plotting One Way Delays between tcp connections
        '''

        parser.epilog = '''
            plot owd tcp examples/client_2_filtered.pcapng 0 examples/server_2_filtered.pcapng 0 --display
        '''

        return parser
コード例 #3
0
    def __init__(self):
        super().__init__()

        video_types_subparsers = TabCompleteExample.video_parser.add_subparsers(
            title='Media Types', dest='type')

        vid_movies_parser = argparse_completer.ACArgumentParser(prog='movies')
        vid_movies_parser.set_defaults(
            func=TabCompleteExample._do_vid_media_movies)

        vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(
            title='Commands', dest='command')

        vid_movies_list_parser = vid_movies_commands_subparsers.add_parser(
            'list')

        vid_movies_list_parser.add_argument('-t',
                                            '--title',
                                            help='Title Filter')
        vid_movies_list_parser.add_argument(
            '-r',
            '--rating',
            help='Rating Filter',
            nargs='+',
            choices=TabCompleteExample.ratings_types)
        # save a reference to the action object
        director_action = vid_movies_list_parser.add_argument(
            '-d', '--director', help='Director Filter')
        actor_action = vid_movies_list_parser.add_argument('-a',
                                                           '--actor',
                                                           help='Actor Filter',
                                                           action='append')

        # tag the action objects with completion providers. This can be a collection or a callable
        setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.static_list_directors)
        setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES,
                query_actors)

        vid_movies_add_parser = vid_movies_commands_subparsers.add_parser(
            'add')
        vid_movies_add_parser.add_argument('title', help='Movie Title')
        vid_movies_add_parser.add_argument(
            'rating',
            help='Movie Rating',
            choices=TabCompleteExample.ratings_types)

        # save a reference to the action object
        director_action = vid_movies_add_parser.add_argument('-d',
                                                             '--director',
                                                             help='Director',
                                                             nargs=(1, 2),
                                                             required=True)
        actor_action = vid_movies_add_parser.add_argument('actor',
                                                          help='Actors',
                                                          nargs='*')

        vid_movies_load_parser = vid_movies_commands_subparsers.add_parser(
            'load')
        vid_movie_file_action = vid_movies_load_parser.add_argument(
            'movie_file', help='Movie database')

        vid_movies_read_parser = vid_movies_commands_subparsers.add_parser(
            'read')
        vid_movie_fread_action = vid_movies_read_parser.add_argument(
            'movie_file', help='Movie database')

        # tag the action objects with completion providers. This can be a collection or a callable
        setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.static_list_directors)
        setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES,
                'instance_query_actors')

        # tag the file property with a custom completion function 'delimeter_complete' provided by cmd2.
        setattr(vid_movie_file_action, argparse_completer.ACTION_ARG_CHOICES,
                ('delimiter_complete', {
                    'delimiter': '/',
                    'match_against': TabCompleteExample.file_list
                }))
        setattr(vid_movie_fread_action, argparse_completer.ACTION_ARG_CHOICES,
                ('path_complete', [False, False]))

        vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser(
            'delete')
        vid_delete_movie_id = vid_movies_delete_parser.add_argument(
            'movie_id', help='Movie ID')
        setattr(vid_delete_movie_id, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.instance_query_movie_ids)
        setattr(vid_delete_movie_id,
                argparse_completer.ACTION_DESCRIPTIVE_COMPLETION_HEADER,
                'Title')

        # Add the 'movies' parser as a parent of sub-parser
        video_types_subparsers.add_parser('movies',
                                          parents=[vid_movies_parser],
                                          add_help=False)

        vid_shows_parser = argparse_completer.ACArgumentParser(prog='shows')
        vid_shows_parser.set_defaults(
            func=TabCompleteExample._do_vid_media_shows)

        vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(
            title='Commands', dest='command')

        vid_shows_list_parser = vid_shows_commands_subparsers.add_parser(
            'list')

        video_types_subparsers.add_parser('shows',
                                          parents=[vid_shows_parser],
                                          add_help=False)
コード例 #4
0
class TabCompleteExample(cmd2.Cmd):
    """ Example cmd2 application where we a base command which has a couple subcommands."""

    CAT_AUTOCOMPLETE = 'AutoComplete Examples'

    def __init__(self):
        super().__init__()

        video_types_subparsers = TabCompleteExample.video_parser.add_subparsers(
            title='Media Types', dest='type')

        vid_movies_parser = argparse_completer.ACArgumentParser(prog='movies')
        vid_movies_parser.set_defaults(
            func=TabCompleteExample._do_vid_media_movies)

        vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(
            title='Commands', dest='command')

        vid_movies_list_parser = vid_movies_commands_subparsers.add_parser(
            'list')

        vid_movies_list_parser.add_argument('-t',
                                            '--title',
                                            help='Title Filter')
        vid_movies_list_parser.add_argument(
            '-r',
            '--rating',
            help='Rating Filter',
            nargs='+',
            choices=TabCompleteExample.ratings_types)
        # save a reference to the action object
        director_action = vid_movies_list_parser.add_argument(
            '-d', '--director', help='Director Filter')
        actor_action = vid_movies_list_parser.add_argument('-a',
                                                           '--actor',
                                                           help='Actor Filter',
                                                           action='append')

        # tag the action objects with completion providers. This can be a collection or a callable
        setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.static_list_directors)
        setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES,
                query_actors)

        vid_movies_add_parser = vid_movies_commands_subparsers.add_parser(
            'add')
        vid_movies_add_parser.add_argument('title', help='Movie Title')
        vid_movies_add_parser.add_argument(
            'rating',
            help='Movie Rating',
            choices=TabCompleteExample.ratings_types)

        # save a reference to the action object
        director_action = vid_movies_add_parser.add_argument('-d',
                                                             '--director',
                                                             help='Director',
                                                             nargs=(1, 2),
                                                             required=True)
        actor_action = vid_movies_add_parser.add_argument('actor',
                                                          help='Actors',
                                                          nargs='*')

        vid_movies_load_parser = vid_movies_commands_subparsers.add_parser(
            'load')
        vid_movie_file_action = vid_movies_load_parser.add_argument(
            'movie_file', help='Movie database')

        vid_movies_read_parser = vid_movies_commands_subparsers.add_parser(
            'read')
        vid_movie_fread_action = vid_movies_read_parser.add_argument(
            'movie_file', help='Movie database')

        # tag the action objects with completion providers. This can be a collection or a callable
        setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.static_list_directors)
        setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES,
                'instance_query_actors')

        # tag the file property with a custom completion function 'delimeter_complete' provided by cmd2.
        setattr(vid_movie_file_action, argparse_completer.ACTION_ARG_CHOICES,
                ('delimiter_complete', {
                    'delimiter': '/',
                    'match_against': TabCompleteExample.file_list
                }))
        setattr(vid_movie_fread_action, argparse_completer.ACTION_ARG_CHOICES,
                ('path_complete', [False, False]))

        vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser(
            'delete')
        vid_delete_movie_id = vid_movies_delete_parser.add_argument(
            'movie_id', help='Movie ID')
        setattr(vid_delete_movie_id, argparse_completer.ACTION_ARG_CHOICES,
                TabCompleteExample.instance_query_movie_ids)
        setattr(vid_delete_movie_id,
                argparse_completer.ACTION_DESCRIPTIVE_COMPLETION_HEADER,
                'Title')

        # Add the 'movies' parser as a parent of sub-parser
        video_types_subparsers.add_parser('movies',
                                          parents=[vid_movies_parser],
                                          add_help=False)

        vid_shows_parser = argparse_completer.ACArgumentParser(prog='shows')
        vid_shows_parser.set_defaults(
            func=TabCompleteExample._do_vid_media_shows)

        vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(
            title='Commands', dest='command')

        vid_shows_list_parser = vid_shows_commands_subparsers.add_parser(
            'list')

        video_types_subparsers.add_parser('shows',
                                          parents=[vid_shows_parser],
                                          add_help=False)

    # For mocking a data source for the example commands
    ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17']
    show_ratings = ['TV-Y', 'TV-Y7', 'TV-G', 'TV-PG', 'TV-14', 'TV-MA']
    static_list_directors = [
        'J. J. Abrams', 'Irvin Kershner', 'George Lucas', 'Richard Marquand',
        'Rian Johnson', 'Gareth Edwards'
    ]
    USER_MOVIE_LIBRARY = ['ROGUE1', 'SW_EP04', 'SW_EP05']
    MOVIE_DATABASE_IDS = [
        'SW_EP1', 'SW_EP02', 'SW_EP03', 'ROGUE1', 'SW_EP04', 'SW_EP05',
        'SW_EP06', 'SW_EP07', 'SW_EP08', 'SW_EP09'
    ]
    MOVIE_DATABASE = {
        'SW_EP04': {
            'title':
            'Star Wars: Episode IV - A New Hope',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP05': {
            'title':
            'Star Wars: Episode V - The Empire Strikes Back',
            'rating':
            'PG',
            'director': ['Irvin Kershner'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP06': {
            'title':
            'Star Wars: Episode VI - Return of the Jedi',
            'rating':
            'PG',
            'director': ['Richard Marquand'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP1': {
            'title':
            'Star Wars: Episode I - The Phantom Menace',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor':
            ['Liam Neeson', 'Ewan McGregor', 'Natalie Portman', 'Jake Lloyd']
        },
        'SW_EP02': {
            'title':
            'Star Wars: Episode II - Attack of the Clones',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor': [
                'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
                'Hayden Christensen', 'Christopher Lee'
            ]
        },
        'SW_EP03': {
            'title':
            'Star Wars: Episode III - Revenge of the Sith',
            'rating':
            'PG-13',
            'director': ['George Lucas'],
            'actor': [
                'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
                'Hayden Christensen'
            ]
        },
    }
    USER_SHOW_LIBRARY = {'SW_REB': ['S01E01', 'S02E02']}
    SHOW_DATABASE_IDS = ['SW_CW', 'SW_TCW', 'SW_REB']
    SHOW_DATABASE = {
        'SW_CW': {
            'title': 'Star Wars: Clone Wars',
            'rating': 'TV-Y7',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
        'SW_TCW': {
            'title': 'Star Wars: The Clone Wars',
            'rating': 'TV-PG',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
        'SW_REB': {
            'title': 'Star Wars: Rebels',
            'rating': 'TV-Y7',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
    }

    file_list = \
        [
            '/home/user/file.db',
            '/home/user/file space.db',
            '/home/user/another.db',
            '/home/other user/maps.db',
            '/home/other user/tests.db'
        ]

    def instance_query_actors(self) -> List[str]:
        """Simulating a function that queries and returns a completion values"""
        return actors

    def instance_query_movie_ids(self) -> List[str]:
        """Demonstrates showing tabular hinting of tab completion information"""
        completions_with_desc = []

        for movie_id in self.MOVIE_DATABASE_IDS:
            if movie_id in self.MOVIE_DATABASE:
                movie_entry = self.MOVIE_DATABASE[movie_id]
                completions_with_desc.append(
                    argparse_completer.CompletionItem(movie_id,
                                                      movie_entry['title']))

        return completions_with_desc

    ###################################################################################
    # The media command demonstrates a completer with multiple layers of subcommands
    #   - This example demonstrates how to tag a completion attribute on each action, enabling argument
    #       completion without implementing a complete_COMMAND function

    def _do_vid_media_movies(self, args) -> None:
        if not args.command:
            self.do_help('video movies')
        elif args.command == 'list':
            for movie_id in TabCompleteExample.MOVIE_DATABASE:
                movie = TabCompleteExample.MOVIE_DATABASE[movie_id]
                print(
                    '{}\n-----------------------------\n{}   ID: {}\nDirector: {}\nCast:\n    {}\n\n'
                    .format(movie['title'], movie['rating'], movie_id,
                            ', '.join(movie['director']),
                            '\n    '.join(movie['actor'])))

    def _do_vid_media_shows(self, args) -> None:
        if not args.command:
            self.do_help('video shows')

        elif args.command == 'list':
            for show_id in TabCompleteExample.SHOW_DATABASE:
                show = TabCompleteExample.SHOW_DATABASE[show_id]
                print('{}\n-----------------------------\n{}   ID: {}'.format(
                    show['title'], show['rating'], show_id))
                for season in show['seasons']:
                    ep_list = show['seasons'][season]
                    print('  Season {}:\n    {}'.format(
                        season, '\n    '.join(ep_list)))
                print()

    video_parser = argparse_completer.ACArgumentParser(prog='video')

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(video_parser)
    def do_video(self, args):
        """Video management command demonstrates multiple layers of subcommands being handled by AutoCompleter"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected
            func(self, args)
        else:
            # No subcommand was provided, so call help
            self.do_help('video')
コード例 #5
0
ファイル: test_pyscript.py プロジェクト: xiaohuta/cmd2
class PyscriptExample(Cmd):
    ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17']

    def _do_media_movies(self, args) -> None:
        if not args.command:
            self.do_help('media movies')
        else:
            self.poutput('media movies ' + str(args.__dict__))

    def _do_media_shows(self, args) -> None:
        if not args.command:
            self.do_help('media shows')

        if not args.command:
            self.do_help('media shows')
        else:
            self.poutput('media shows ' + str(args.__dict__))

    media_parser = argparse_completer.ACArgumentParser(prog='media')

    media_types_subparsers = media_parser.add_subparsers(title='Media Types',
                                                         dest='type')

    movies_parser = media_types_subparsers.add_parser('movies')
    movies_parser.set_defaults(func=_do_media_movies)

    movies_commands_subparsers = movies_parser.add_subparsers(title='Commands',
                                                              dest='command')

    movies_list_parser = movies_commands_subparsers.add_parser('list')

    movies_list_parser.add_argument('-t', '--title', help='Title Filter')
    movies_list_parser.add_argument('-r',
                                    '--rating',
                                    help='Rating Filter',
                                    nargs='+',
                                    choices=ratings_types)
    movies_list_parser.add_argument('-d', '--director', help='Director Filter')
    movies_list_parser.add_argument('-a',
                                    '--actor',
                                    help='Actor Filter',
                                    action='append')

    movies_add_parser = movies_commands_subparsers.add_parser('add')
    movies_add_parser.add_argument('title', help='Movie Title')
    movies_add_parser.add_argument('rating',
                                   help='Movie Rating',
                                   choices=ratings_types)
    movies_add_parser.add_argument('-d',
                                   '--director',
                                   help='Director',
                                   nargs=(1, 2),
                                   required=True)
    movies_add_parser.add_argument('actor', help='Actors', nargs='*')

    movies_delete_parser = movies_commands_subparsers.add_parser('delete')

    shows_parser = media_types_subparsers.add_parser('shows')
    shows_parser.set_defaults(func=_do_media_shows)

    shows_commands_subparsers = shows_parser.add_subparsers(title='Commands',
                                                            dest='command')

    shows_list_parser = shows_commands_subparsers.add_parser('list')

    @with_argparser(media_parser)
    def do_media(self, args):
        """Media management command demonstrates multiple layers of sub-commands being handled by AutoCompleter"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected
            func(self, args)
        else:
            # No subcommand was provided, so call help
            self.do_help('media')

    foo_parser = argparse_completer.ACArgumentParser(prog='foo')
    foo_parser.add_argument('-c', dest='counter', action='count')
    foo_parser.add_argument('-t', dest='trueval', action='store_true')
    foo_parser.add_argument('-n',
                            dest='constval',
                            action='store_const',
                            const=42)
    foo_parser.add_argument('variable', nargs=(2, 3))
    foo_parser.add_argument('optional', nargs='?')
    foo_parser.add_argument('zeroormore', nargs='*')

    @with_argparser(foo_parser)
    def do_foo(self, args):
        self.poutput('foo ' + str(sorted(args.__dict__)))
        if self._in_py:
            FooResult = namedtuple_with_defaults('FooResult', [
                'counter', 'trueval', 'constval', 'variable', 'optional',
                'zeroormore'
            ])
            self._last_result = FooResult(
                **{
                    'counter': args.counter,
                    'trueval': args.trueval,
                    'constval': args.constval,
                    'variable': args.variable,
                    'optional': args.optional,
                    'zeroormore': args.zeroormore
                })

    bar_parser = argparse_completer.ACArgumentParser(prog='bar')
    bar_parser.add_argument('first')
    bar_parser.add_argument('oneormore', nargs='+')
    bar_parser.add_argument('-a', dest='aaa')

    @with_argparser(bar_parser)
    def do_bar(self, args):
        out = 'bar '
        arg_dict = args.__dict__
        keys = list(arg_dict.keys())
        keys.sort()
        out += '{'
        for key in keys:
            out += "'{}':'{}'".format(key, arg_dict[key])
        self.poutput(out)
コード例 #6
0
ファイル: tab_autocompletion.py プロジェクト: nlpia/cmd2
class TabCompleteExample(cmd2.Cmd):
    """ Example cmd2 application where we a base command which has a couple sub-commands."""

    CAT_AUTOCOMPLETE = 'AutoComplete Examples'

    def __init__(self):
        super().__init__()

    # For mocking a data source for the example commands
    ratings_types = ['G', 'PG', 'PG-13', 'R', 'NC-17']
    show_ratings = ['TV-Y', 'TV-Y7', 'TV-G', 'TV-PG', 'TV-14', 'TV-MA']
    static_list_directors = [
        'J. J. Abrams', 'Irvin Kershner', 'George Lucas', 'Richard Marquand',
        'Rian Johnson', 'Gareth Edwards'
    ]
    USER_MOVIE_LIBRARY = ['ROGUE1', 'SW_EP04', 'SW_EP05']
    MOVIE_DATABASE_IDS = [
        'SW_EP1', 'SW_EP02', 'SW_EP03', 'ROGUE1', 'SW_EP04', 'SW_EP05',
        'SW_EP06', 'SW_EP07', 'SW_EP08', 'SW_EP09'
    ]
    MOVIE_DATABASE = {
        'SW_EP04': {
            'title':
            'Star Wars: Episode IV - A New Hope',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP05': {
            'title':
            'Star Wars: Episode V - The Empire Strikes Back',
            'rating':
            'PG',
            'director': ['Irvin Kershner'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP06': {
            'title':
            'Star Wars: Episode VI - Return of the Jedi',
            'rating':
            'PG',
            'director': ['Richard Marquand'],
            'actor': [
                'Mark Hamill', 'Harrison Ford', 'Carrie Fisher',
                'Alec Guinness', 'Peter Mayhew', 'Anthony Daniels'
            ]
        },
        'SW_EP1': {
            'title':
            'Star Wars: Episode I - The Phantom Menace',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor':
            ['Liam Neeson', 'Ewan McGregor', 'Natalie Portman', 'Jake Lloyd']
        },
        'SW_EP02': {
            'title':
            'Star Wars: Episode II - Attack of the Clones',
            'rating':
            'PG',
            'director': ['George Lucas'],
            'actor': [
                'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
                'Hayden Christensen', 'Christopher Lee'
            ]
        },
        'SW_EP03': {
            'title':
            'Star Wars: Episode III - Revenge of the Sith',
            'rating':
            'PG-13',
            'director': ['George Lucas'],
            'actor': [
                'Liam Neeson', 'Ewan McGregor', 'Natalie Portman',
                'Hayden Christensen'
            ]
        },
    }
    USER_SHOW_LIBRARY = {'SW_REB': ['S01E01', 'S02E02']}
    SHOW_DATABASE_IDS = ['SW_CW', 'SW_TCW', 'SW_REB']
    SHOW_DATABASE = {
        'SW_CW': {
            'title': 'Star Wars: Clone Wars',
            'rating': 'TV-Y7',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
        'SW_TCW': {
            'title': 'Star Wars: The Clone Wars',
            'rating': 'TV-PG',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
        'SW_REB': {
            'title': 'Star Wars: Rebels',
            'rating': 'TV-Y7',
            'seasons': {
                1: ['S01E01', 'S01E02', 'S01E03'],
                2: ['S02E01', 'S02E02', 'S02E03']
            }
        },
    }

    file_list = \
        [
            '/home/user/file.db',
            '/home/user/file space.db',
            '/home/user/another.db',
            '/home/other user/maps.db',
            '/home/other user/tests.db'
        ]

    def instance_query_actors(self) -> List[str]:
        """Simulating a function that queries and returns a completion values"""
        return actors

    def instance_query_movie_ids(self) -> List[str]:
        """Demonstrates showing tabular hinting of tab completion information"""
        completions_with_desc = []

        # Sort the movie id strings with a natural sort since they contain numbers
        for movie_id in utils.natural_sort(self.MOVIE_DATABASE_IDS):
            if movie_id in self.MOVIE_DATABASE:
                movie_entry = self.MOVIE_DATABASE[movie_id]
                completions_with_desc.append(
                    argparse_completer.CompletionItem(movie_id,
                                                      movie_entry['title']))

        # Mark that we already sorted the matches
        self.matches_sorted = True
        return completions_with_desc

    # This demonstrates a number of customizations of the AutoCompleter version of ArgumentParser
    #  - The help output will separately group required vs optional flags
    #  - The help output for arguments with multiple flags or with append=True is more concise
    #  - ACArgumentParser adds the ability to specify ranges of argument counts in 'nargs'

    suggest_description = "Suggest command demonstrates argparse customizations.\n"
    suggest_description += "See hybrid_suggest and orig_suggest to compare the help output."
    suggest_parser = argparse_completer.ACArgumentParser(
        description=suggest_description)

    suggest_parser.add_argument('-t',
                                '--type',
                                choices=['movie', 'show'],
                                required=True)
    suggest_parser.add_argument('-d',
                                '--duration',
                                nargs=(1, 2),
                                action='append',
                                help='Duration constraint in minutes.\n'
                                '\tsingle value - maximum duration\n'
                                '\t[a, b] - duration range')

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(suggest_parser)
    def do_suggest(self, args) -> None:
        """Suggest command demonstrates argparse customizations"""
        if not args.type:
            self.do_help('suggest')

    # If you prefer the original argparse help output but would like narg ranges, it's possible
    # to enable narg ranges without the help changes using this method

    suggest_parser_hybrid = argparse.ArgumentParser()
    # This registers the custom narg range handling
    argparse_completer.register_custom_actions(suggest_parser_hybrid)

    suggest_parser_hybrid.add_argument('-t',
                                       '--type',
                                       choices=['movie', 'show'],
                                       required=True)
    suggest_parser_hybrid.add_argument('-d',
                                       '--duration',
                                       nargs=(1, 2),
                                       action='append',
                                       help='Duration constraint in minutes.\n'
                                       '\tsingle value - maximum duration\n'
                                       '\t[a, b] - duration range')

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(suggest_parser_hybrid)
    def do_hybrid_suggest(self, args):
        if not args.type:
            self.do_help('orig_suggest')

    # This variant demonstrates the AutoCompleter working with the orginial argparse.
    # Base argparse is unable to specify narg ranges. Autocompleter will keep expecting additional arguments
    # for the -d/--duration flag until you specify a new flag or end processing of flags with '--'

    suggest_parser_orig = argparse.ArgumentParser()

    suggest_parser_orig.add_argument('-t',
                                     '--type',
                                     choices=['movie', 'show'],
                                     required=True)
    suggest_parser_orig.add_argument('-d',
                                     '--duration',
                                     nargs='+',
                                     action='append',
                                     help='Duration constraint in minutes.\n'
                                     '\tsingle value - maximum duration\n'
                                     '\t[a, b] - duration range')

    @cmd2.with_argparser(suggest_parser_orig)
    @cmd2.with_category(CAT_AUTOCOMPLETE)
    def do_orig_suggest(self, args) -> None:
        if not args.type:
            self.do_help('orig_suggest')

    ###################################################################################
    # The media command demonstrates a completer with multiple layers of subcommands
    #   - This example demonstrates how to tag a completion attribute on each action, enabling argument
    #       completion without implementing a complete_COMMAND function

    def _do_vid_media_movies(self, args) -> None:
        if not args.command:
            self.do_help('media movies')
        elif args.command == 'list':
            for movie_id in TabCompleteExample.MOVIE_DATABASE:
                movie = TabCompleteExample.MOVIE_DATABASE[movie_id]
                print(
                    '{}\n-----------------------------\n{}   ID: {}\nDirector: {}\nCast:\n    {}\n\n'
                    .format(movie['title'], movie['rating'], movie_id,
                            ', '.join(movie['director']),
                            '\n    '.join(movie['actor'])))

    def _do_vid_media_shows(self, args) -> None:
        if not args.command:
            self.do_help('media shows')

        elif args.command == 'list':
            for show_id in TabCompleteExample.SHOW_DATABASE:
                show = TabCompleteExample.SHOW_DATABASE[show_id]
                print('{}\n-----------------------------\n{}   ID: {}'.format(
                    show['title'], show['rating'], show_id))
                for season in show['seasons']:
                    ep_list = show['seasons'][season]
                    print('  Season {}:\n    {}'.format(
                        season, '\n    '.join(ep_list)))
                print()

    video_parser = argparse_completer.ACArgumentParser(prog='media')

    video_types_subparsers = video_parser.add_subparsers(title='Media Types',
                                                         dest='type')

    vid_movies_parser = video_types_subparsers.add_parser('movies')
    vid_movies_parser.set_defaults(func=_do_vid_media_movies)

    vid_movies_commands_subparsers = vid_movies_parser.add_subparsers(
        title='Commands', dest='command')

    vid_movies_list_parser = vid_movies_commands_subparsers.add_parser('list')

    vid_movies_list_parser.add_argument('-t', '--title', help='Title Filter')
    vid_movies_list_parser.add_argument('-r',
                                        '--rating',
                                        help='Rating Filter',
                                        nargs='+',
                                        choices=ratings_types)
    # save a reference to the action object
    director_action = vid_movies_list_parser.add_argument(
        '-d', '--director', help='Director Filter')
    actor_action = vid_movies_list_parser.add_argument('-a',
                                                       '--actor',
                                                       help='Actor Filter',
                                                       action='append')

    # tag the action objects with completion providers. This can be a collection or a callable
    setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
            static_list_directors)
    setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES, query_actors)

    vid_movies_add_parser = vid_movies_commands_subparsers.add_parser('add')
    vid_movies_add_parser.add_argument('title', help='Movie Title')
    vid_movies_add_parser.add_argument('rating',
                                       help='Movie Rating',
                                       choices=ratings_types)

    # save a reference to the action object
    director_action = vid_movies_add_parser.add_argument('-d',
                                                         '--director',
                                                         help='Director',
                                                         nargs=(1, 2),
                                                         required=True)
    actor_action = vid_movies_add_parser.add_argument('actor',
                                                      help='Actors',
                                                      nargs='*')

    vid_movies_load_parser = vid_movies_commands_subparsers.add_parser('load')
    vid_movie_file_action = vid_movies_load_parser.add_argument(
        'movie_file', help='Movie database')

    vid_movies_read_parser = vid_movies_commands_subparsers.add_parser('read')
    vid_movie_fread_action = vid_movies_read_parser.add_argument(
        'movie_file', help='Movie database')

    # tag the action objects with completion providers. This can be a collection or a callable
    setattr(director_action, argparse_completer.ACTION_ARG_CHOICES,
            static_list_directors)
    setattr(actor_action, argparse_completer.ACTION_ARG_CHOICES,
            'instance_query_actors')

    # tag the file property with a custom completion function 'delimiter_complete' provided by cmd2.
    setattr(vid_movie_file_action, argparse_completer.ACTION_ARG_CHOICES,
            ('delimiter_complete', {
                'delimiter': '/',
                'match_against': file_list
            }))
    setattr(vid_movie_fread_action, argparse_completer.ACTION_ARG_CHOICES,
            ('path_complete', ))

    vid_movies_delete_parser = vid_movies_commands_subparsers.add_parser(
        'delete')
    vid_delete_movie_id = vid_movies_delete_parser.add_argument(
        'movie_id', help='Movie ID')
    setattr(vid_delete_movie_id, argparse_completer.ACTION_ARG_CHOICES,
            instance_query_movie_ids)
    setattr(vid_delete_movie_id,
            argparse_completer.ACTION_DESCRIPTIVE_COMPLETION_HEADER, 'Title')

    vid_shows_parser = video_types_subparsers.add_parser('shows')
    vid_shows_parser.set_defaults(func=_do_vid_media_shows)

    vid_shows_commands_subparsers = vid_shows_parser.add_subparsers(
        title='Commands', dest='command')

    vid_shows_list_parser = vid_shows_commands_subparsers.add_parser('list')

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(video_parser)
    def do_video(self, args):
        """Video management command demonstrates multiple layers of sub-commands being handled by AutoCompleter"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected
            func(self, args)
        else:
            # No subcommand was provided, so call help
            self.do_help('video')

    ###################################################################################
    # The media command demonstrates a completer with multiple layers of subcommands
    #   - This example uses a flat completion lookup dictionary

    def _do_media_movies(self, args) -> None:
        if not args.command:
            self.do_help('media movies')
        elif args.command == 'list':
            for movie_id in TabCompleteExample.MOVIE_DATABASE:
                movie = TabCompleteExample.MOVIE_DATABASE[movie_id]
                print(
                    '{}\n-----------------------------\n{}   ID: {}\nDirector: {}\nCast:\n    {}\n\n'
                    .format(movie['title'], movie['rating'], movie_id,
                            ', '.join(movie['director']),
                            '\n    '.join(movie['actor'])))
        elif args.command == 'add':
            print(
                'Adding Movie\n----------------\nTitle: {}\nRating: {}\nDirectors: {}\nActors: {}\n\n'
                .format(args.title, args.rating, ', '.join(args.director),
                        ', '.join(args.actor)))

    def _do_media_shows(self, args) -> None:
        if not args.command:
            self.do_help('media shows')

        elif args.command == 'list':
            for show_id in TabCompleteExample.SHOW_DATABASE:
                show = TabCompleteExample.SHOW_DATABASE[show_id]
                print('{}\n-----------------------------\n{}   ID: {}'.format(
                    show['title'], show['rating'], show_id))
                for season in show['seasons']:
                    ep_list = show['seasons'][season]
                    print('  Season {}:\n    {}'.format(
                        season, '\n    '.join(ep_list)))
                print()

    media_parser = argparse_completer.ACArgumentParser(prog='media')

    media_types_subparsers = media_parser.add_subparsers(title='Media Types',
                                                         dest='type')

    movies_parser = media_types_subparsers.add_parser('movies')
    movies_parser.set_defaults(func=_do_media_movies)

    movies_commands_subparsers = movies_parser.add_subparsers(title='Commands',
                                                              dest='command')

    movies_list_parser = movies_commands_subparsers.add_parser('list')

    movies_list_parser.add_argument('-t', '--title', help='Title Filter')
    movies_list_parser.add_argument('-r',
                                    '--rating',
                                    help='Rating Filter',
                                    nargs='+',
                                    choices=ratings_types)
    movies_list_parser.add_argument('-d', '--director', help='Director Filter')
    movies_list_parser.add_argument('-a',
                                    '--actor',
                                    help='Actor Filter',
                                    action='append')

    movies_add_parser = movies_commands_subparsers.add_parser('add')
    movies_add_parser.add_argument('title', help='Movie Title')
    movies_add_parser.add_argument('rating',
                                   help='Movie Rating',
                                   choices=ratings_types)
    movies_add_parser.add_argument('-d',
                                   '--director',
                                   help='Director',
                                   nargs=(1, 2),
                                   required=True)
    movies_add_parser.add_argument('actor',
                                   help='Actors',
                                   nargs=argparse.REMAINDER)

    movies_delete_parser = movies_commands_subparsers.add_parser('delete')
    movies_delete_movie_id = movies_delete_parser.add_argument('movie_id',
                                                               help='Movie ID')
    setattr(movies_delete_movie_id, argparse_completer.ACTION_ARG_CHOICES,
            'instance_query_movie_ids')
    setattr(movies_delete_movie_id,
            argparse_completer.ACTION_DESCRIPTIVE_COMPLETION_HEADER, 'Title')

    movies_load_parser = movies_commands_subparsers.add_parser('load')
    movie_file_action = movies_load_parser.add_argument('movie_file',
                                                        help='Movie database')

    shows_parser = media_types_subparsers.add_parser('shows')
    shows_parser.set_defaults(func=_do_media_shows)

    shows_commands_subparsers = shows_parser.add_subparsers(title='Commands',
                                                            dest='command')

    shows_list_parser = shows_commands_subparsers.add_parser('list')

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(media_parser)
    def do_media(self, args):
        """Media management command demonstrates multiple layers of sub-commands being handled by AutoCompleter"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected
            func(self, args)
        else:
            # No subcommand was provided, so call help
            self.do_help('media')

    # This completer is implemented using a single dictionary to look up completion lists for all layers of
    # subcommands. For each argument, AutoCompleter will search for completion values from the provided
    # arg_choices dict. This requires careful naming of argparse arguments so that there are no unintentional
    # name collisions.
    def complete_media(self, text, line, begidx, endidx):
        """ Adds tab completion to media"""
        choices = {
            'actor': query_actors,  # function
            'director':
            TabCompleteExample.static_list_directors,  # static list
            'movie_file': (self.path_complete, )
        }
        completer = argparse_completer.AutoCompleter(
            TabCompleteExample.media_parser, self, arg_choices=choices)

        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
        results = completer.complete_command(tokens, text, line, begidx,
                                             endidx)

        return results

    ###################################################################################
    # The library command demonstrates a completer with multiple layers of subcommands
    # with different completion results per sub-command
    #   - This demonstrates how to build a tree of completion lookups to pass down
    #
    # Only use this method if you absolutely need to as it dramatically
    # increases the complexity and decreases readability.

    def _do_library_movie(self, args):
        if not args.type or not args.command:
            self.do_help('library movie')

    def _do_library_show(self, args):
        if not args.type:
            self.do_help('library show')

    def _query_movie_database(self):
        return list(
            set(TabCompleteExample.MOVIE_DATABASE_IDS).difference(
                set(TabCompleteExample.USER_MOVIE_LIBRARY)))

    def _query_movie_user_library(self):
        return TabCompleteExample.USER_MOVIE_LIBRARY

    def _filter_library(self, text, line, begidx, endidx, full, exclude=()):
        candidates = list(set(full).difference(set(exclude)))
        return [entry for entry in candidates if entry.startswith(text)]

    library_parser = argparse_completer.ACArgumentParser(prog='library')

    library_subcommands = library_parser.add_subparsers(title='Media Types',
                                                        dest='type')

    library_movie_parser = library_subcommands.add_parser('movie')
    library_movie_parser.set_defaults(func=_do_library_movie)

    library_movie_subcommands = library_movie_parser.add_subparsers(
        title='Command', dest='command')

    library_movie_add_parser = library_movie_subcommands.add_parser('add')
    library_movie_add_parser.add_argument('movie_id',
                                          help='ID of movie to add',
                                          action='append')
    library_movie_add_parser.add_argument('-b',
                                          '--borrowed',
                                          action='store_true')

    library_movie_remove_parser = library_movie_subcommands.add_parser(
        'remove')
    library_movie_remove_parser.add_argument('movie_id',
                                             help='ID of movie to remove',
                                             action='append')

    library_show_parser = library_subcommands.add_parser('show')
    library_show_parser.set_defaults(func=_do_library_show)

    library_show_subcommands = library_show_parser.add_subparsers(
        title='Command', dest='command')

    library_show_add_parser = library_show_subcommands.add_parser('add')
    library_show_add_parser.add_argument('show_id', help='Show IDs to add')
    library_show_add_parser.add_argument('episode_id',
                                         nargs='*',
                                         help='Show IDs to add')

    library_show_rmv_parser = library_show_subcommands.add_parser('remove')

    # Demonstrates a custom completion function that does more with the command line than is
    # allowed by the standard completion functions
    def _filter_episodes(self, text, line, begidx, endidx, show_db, user_lib):
        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
        show_id = tokens[3]
        if show_id:
            if show_id in show_db:
                show = show_db[show_id]
                all_episodes = itertools.chain(*(show['seasons'].values()))

                if show_id in user_lib:
                    user_eps = user_lib[show_id]
                else:
                    user_eps = []

                return self._filter_library(text, line, begidx, endidx,
                                            all_episodes, user_eps)
        return []

    @cmd2.with_category(CAT_AUTOCOMPLETE)
    @cmd2.with_argparser(library_parser)
    def do_library(self, args):
        """Media management command demonstrates multiple layers of sub-commands being handled by AutoCompleter"""
        func = getattr(args, 'func', None)
        if func is not None:
            # Call whatever subcommand function was selected
            func(self, args)
        else:
            # No subcommand was provided, so call help
            self.do_help('library')

    def complete_library(self, text, line, begidx, endidx):

        # this demonstrates the much more complicated scenario of having
        # unique completion parameters per sub-command that use the same
        # argument name. To do this we build a multi-layer nested tree
        # of lookups far AutoCompleter to traverse. This nested tree must
        # match the structure of the argparse parser
        #

        movie_add_choices = {'movie_id': self._query_movie_database}
        movie_remove_choices = {'movie_id': self._query_movie_user_library}

        # This demonstrates the ability to mix custom completion functions with argparse completion.
        # By specifying a tuple for a completer, AutoCompleter expects a custom completion function
        # with optional index-based as well as keyword based arguments. This is an alternative to using
        # a partial function.

        show_add_choices = {
            'show_id': (
                self._filter_library,  # This is a custom completion function
                # This tuple represents index-based args to append to the function call
                (
                    list(TabCompleteExample.SHOW_DATABASE.keys()), )),
            'episode_id': (
                self._filter_episodes,  # this is a custom completion function
                # this list represents index-based args to append to the function call
                [TabCompleteExample.SHOW_DATABASE],
                # this dict contains keyword-based args to append to the function call
                {
                    'user_lib': TabCompleteExample.USER_SHOW_LIBRARY
                })
        }
        show_remove_choices = {}

        # The library movie sub-parser group 'command' has 2 sub-parsers:
        #   'add' and 'remove'
        library_movie_command_params = \
            {'add': (movie_add_choices, None),
             'remove': (movie_remove_choices, None)}

        library_show_command_params = \
            {'add': (show_add_choices, None),
             'remove': (show_remove_choices, None)}

        # The 'library movie' command has a sub-parser group called 'command'
        library_movie_subcommand_groups = {
            'command': library_movie_command_params
        }
        library_show_subcommand_groups = {
            'command': library_show_command_params
        }

        # Mapping of a specific sub-parser of the 'type' group to a tuple. Each
        #    tuple has 2 values corresponding what's passed to the constructor
        #    parameters (arg_choices,subcmd_args_lookup) of the nested
        #    instance of AutoCompleter
        library_type_params = {
            'movie': (None, library_movie_subcommand_groups),
            'show': (None, library_show_subcommand_groups)
        }

        # maps the a subcommand group to a dictionary mapping a specific
        # sub-command to a tuple of (arg_choices, subcmd_args_lookup)
        #
        # In this example, 'library_parser' has a sub-parser group called 'type'
        #   under the type sub-parser group, there are 2 sub-parsers: 'movie', 'show'
        library_subcommand_groups = {'type': library_type_params}

        completer = argparse_completer.AutoCompleter(
            TabCompleteExample.library_parser,
            self,
            subcmd_args_lookup=library_subcommand_groups)

        tokens, _ = self.tokens_for_completion(line, begidx, endidx)
        results = completer.complete_command(tokens, text, line, begidx,
                                             endidx)

        return results
コード例 #7
0
class MpTcpAnalyzerCmdApp(cmd2.Cmd):
    """
    mptcpanalyzer can run into 3 modes:

    #. interactive mode (default): an interpreter with some basic completion will accept your commands.
    There is also some help embedded.
    #. if a filename is passed as argument, it will load commands from
    this file otherwise, it will consider the unknow arguments as one command,
     the same that could be used interactively
    """

    intro = textwrap.dedent("""
        Press ? to list the available commands and `help <command>` or `<command> -h`
        for a detailed help of the command
        """.format(__version__))

    def stevedore_error_handler(manager, entrypoint, exception):
        print("Error while loading entrypoint [%s]" % entrypoint)

    def __init__(self, cfg: MpTcpAnalyzerConfig, stdin=sys.stdin, **kwargs) -> None:
        """
        Args:
            cfg (MpTcpAnalyzerConfig): A valid configuration

        Attributes:
            prompt (str): Prompt seen by the user, displays currently loaded pcpa
            config: configution to get user parameters
            data:  dataframe currently in use
        """

        self.shortcuts.update({
            'lc': 'list_connections',
            'ls': 'list_subflows',
            'lr': 'list_reinjections'
        })
        super().__init__(completekey='tab', stdin=stdin)
        self.prompt = FG_COLORS['blue'] + "Ready>"  + color_off
        self.data = None  # type: pd.DataFrame
        self.config = cfg
        self.tshark_config = TsharkConfig(
            delimiter=cfg["mptcpanalyzer"]["delimiter"],
            profile=cfg["mptcpanalyzer"]["wireshark_profile"],
        )

        # cmd2 specific initialization
        self.abbrev = True  # when no ambiguities, run the command
        self.allow_cli_args = True  # disable autoload of transcripts
        self.allow_redirection = True  # allow pipes in commands
        self.default_to_shell = False
        self.debug = True  # for now
        self.set_posix_shlex = True  # need cmd2 >= 0.8

        #  Load Plots
        ######################
        # you can  list available plots under the namespace
        # https://pypi.python.org/pypi/entry_point_inspector
        # https://docs.openstack.org/stevedore/latest/reference/index.html#stevedore.extension.ExtensionManager
        # mgr = driver.DriverManager(
        self.plot_mgr = extension.ExtensionManager(
            namespace='mptcpanalyzer.plots',
            invoke_on_load=True,
            verify_requirements=True,
            invoke_args=(self.tshark_config,),
            # invoke_kwds
            propagate_map_exceptions=True,
            on_load_failure_callback=self.stevedore_error_handler
        )

        self.cmd_mgr = extension.ExtensionManager(
            namespace='mptcpanalyzer.cmds',
            invoke_on_load=True,
            verify_requirements=True,
            invoke_args=(),
            propagate_map_exceptions=False,
            on_load_failure_callback=self.stevedore_error_handler
        )

        #  do_plot parser
        ######################
        # not my first choice but to accomodate cmd2 constraints
        # see https://github.com/python-cmd2/cmd2/issues/498
        subparsers = MpTcpAnalyzerCmdApp.plot_parser.add_subparsers(dest="plot_type",
            title="Subparsers", help='sub-command help',)
        subparsers.required = True  # type: ignore

        def register_plots(ext, subparsers):
            """Adds a parser per plot"""
            # check if dat is loaded
            parser = ext.obj.default_parser()
            assert parser, "Forgot to return parser"
            subparsers.add_parser(ext.name, parents=[parser], add_help=False)

        self.plot_mgr.map(register_plots, subparsers)
        # # will raise NoMatches when no plot available

        # if loading commands from a file, we disable prompt not to pollute output
        if stdin != sys.stdin:
            log.info("Disabling prompt because reading from stdin")
            self.use_rawinput = False
            self.prompt = ""
            self.intro = ""

        """
        The optional arguments stdin and stdout specify the input and
        output file objects that the Cmd instance or subclass instance will
        use for input and output. If not specified, they will default to
        sys.stdin and sys.stdout.
        """
        print("WARNING: mptcpanalyzer may require a custom wireshark. "
            "Check github for mptcp patches streaming.")

    @property
    def plot_manager(self):
        return self.plot_mgr

    @plot_manager.setter
    def plot_manager(self, mgr):
        """
        Override the default plot manager, only used for testing
        :param mgr: a stevedore plugin manager
        """
        self.plot_mgr = mgr

    def load_plugins(self, mgr=None):
        """
        This function monkey patches the class to inject Command plugins

        Attrs:
            mgr: override the default plugin manager when set.

        Useful to run tests
        """
        mgr = mgr if mgr is not None else self.cmd_mgr

        def _inject_cmd(ext, data):
            log.debug("Injecting plugin %s" % ext.name)
            for prefix in ["do", "help", "complete"]:
                method_name = prefix + "_" + ext.name
                try:
                    obj = getattr(ext.obj, prefix)
                    if obj:
                        setattr(MpTcpAnalyzerCmdApp, method_name, obj)
                except AttributeError:
                    log.debug("Plugin does not provide %s" % method_name)

        # there is also map_method available
        try:
            mgr.map(_inject_cmd, self)
        except stevedore.exception.NoMatches as e:
            log.error("stevedore: No matches (%s)" % e)

    def precmd(self, line):
        """
        Here we can preprocess line, with for instance shlex.split() ?
        Note:
            This is only called when using cmdloop, not with onecmd !
        """
        # default behavior
        print(">>> %s" % line)
        return line

    def cmdloop(self, intro=None):
        """
        overrides baseclass just to be able to catch exceptions
        """
        try:
            super().cmdloop()
        except KeyboardInterrupt as e:
            pass

        # Exception raised by sys.exit(), which is called by argparse
        # we don't want the program to finish just when there is an input error
        except SystemExit as e:
            self.cmdloop()
        except mp.MpTcpException as e:
            print(e)
            self.cmdloop()
        except Exception as e:
            log.critical("Unknown error, aborting...")
            log.critical("%s" % e)
            print("Displaying backtrace:\n")
            traceback.print_exc()

    def postcmd(self, stop, line):
        """
        Override baseclass
        returning true will stop the program
        """
        log.debug("postcmd result for line [%s] => %r", line, stop)

        return True if stop is True else False

    parser = MpTcpAnalyzerParser(description="List subflows of an MPTCP connection")
    filter_stream = parser.add_argument("mptcpstream", action="store", type=int,
        help="Equivalent to wireshark mptcp.stream id")
    # TODO for tests only, fix
    setattr(filter_stream, argparse_completer.ACTION_ARG_CHOICES, [0, 1, 2])

    @with_argparser(parser)
    @with_category(CAT_MPTCP)
    @is_loaded
    def do_list_subflows(self, args):
        """
        list mptcp subflows
                [mptcp.stream id]

        Example:
            ls 0
        """
        self.list_subflows(args.mptcpstream)

    @is_loaded
    def list_subflows(self, mptcpstreamid: int):

        try:
            con = MpTcpConnection.build_from_dataframe(self.data, mptcpstreamid)
            self.poutput("mptcp.stream %d has %d subflow(s) (client/server): " % (mptcpstreamid, len(con.subflows())))
            for sf in con.subflows():
                self.poutput("\t%s" % sf)
        except mp.MpTcpException as e:
            self.perror(e)

    # def help_list_subflows(self):
    #     print("Use parser -h")

    # def complete_list_subflows(self, text, line, begidx, endidx):
    #     """ help to complete the args """
    #     # conversion to set removes duplicate keys
    #     l = list(set(self.data["mptcpstream"]))
    #     # convert items to str else it won't be used for completion
    #     l = [str(x) for x in l]

    #     return l




    # parser = gen_pcap_parser({"pcap": PreprocessingActions.FilterStream | PreprocessingActions.Merge }, protocol="tcp")
    parser = argparse_completer.ACArgumentParser(
    description='''
        This function tries to map a tcp.stream id from one pcap
        to one in another pcap in another dataframe.
    '''
    )

    # TODO could use LoadSinglePcap
    load_pcap1 = parser.add_argument("pcap1", action="store", help="first to load")
    load_pcap2 = parser.add_argument("pcap2", action="store", help="second pcap")

    # cmd2.Cmd.path_complete ?
    # setattr(action_stream, argparse_completer.ACTION_ARG_CHOICES, range(0, 10))
    # use path_filter
    setattr(load_pcap1, argparse_completer.ACTION_ARG_CHOICES, ('path_complete', ))
    setattr(load_pcap2, argparse_completer.ACTION_ARG_CHOICES, ('path_complete', ))

    parser.add_argument("tcpstreamid", action="store", type=int,
        help="tcp.stream id visible in wireshark for pcap1")
    parser.add_argument("--json", action="store_true", default=False,
        help="Machine readable summary.")
    parser.add_argument( '-v', '--verbose', dest="verbose", default=False, action="store_true",
        help="how to display each connection")

    parser.epilog = '''
    Examples:
        map_tcp_connection examples/client_1_tcp_only.pcap examples/server_1_tcp_only.pcap  0
    '''

    @with_argparser(parser)
    @with_category(CAT_TCP)
    def do_map_tcp_connection(self, args):

        df1 = load_into_pandas(args.pcap1, self.tshark_config)
        df2 = load_into_pandas(args.pcap2, self.tshark_config)

        main_connection = TcpConnection.build_from_dataframe(df1, args.tcpstreamid)

        mappings = map_tcp_stream(df2, main_connection)

        self.poutput("Trying to map %s" % (main_connection,))
        self.poutput("%d mapping(s) found" % len(mappings))

        for match in mappings:

            # formatted_output = main.format_mapping(match)
            # output = "{c1.tcpstreamid} <-> {c2.tcpstreamid} with score={score}"
            # formatted_output = output.format(
            #     c1=main_connection,
            #     c2=match,
            #     score=score
            # )
            # print(formatted_output)
            self.poutput("%s" % str(match))



    parser = MpTcpAnalyzerParser(
        description="This function tries to map a mptcp.stream from a dataframe"
                    "(aka pcap) to mptcp.stream"
                    "in another dataframe. "
    )

    load_pcap1 = parser.add_argument("pcap1", action="store", type=str, help="first to load")
    load_pcap2 = parser.add_argument("pcap2", action="store", type=str, help="second pcap")

    setattr(load_pcap1, argparse_completer.ACTION_ARG_CHOICES, ('path_complete', ))
    setattr(load_pcap2, argparse_completer.ACTION_ARG_CHOICES, ('path_complete', ))
    parser.add_argument("mptcpstreamid", action="store", type=int, help="to filter")
    parser.add_argument("--trim", action="store", type=float, default=0, 
            help="Remove mappings with a score below this threshold")
    parser.add_argument("--limit", action="store", type=int, default=2,
            help="Limit display to the --limit best mappings")
    parser.add_argument( '-v', '--verbose', dest="verbose", default=False, action="store_true",
        help="display all candidates")

    @with_argparser(parser)
    @with_category(CAT_MPTCP)
    @experimental
    def do_map_mptcp_connection(self, args):
        """
        Tries to map mptcp.streams from different pcaps.
        Score based mechanism

        Todo:
            - Limit number of displayed matches
        """

        df1 = load_into_pandas(args.pcap1, self.tshark_config)
        df2 = load_into_pandas(args.pcap2, self.tshark_config)


        main_connection = MpTcpConnection.build_from_dataframe(df1, args.mptcpstreamid)
        mappings = map_mptcp_connection(df2, main_connection)


        self.poutput("%d mapping(s) found" % len(mappings))
        mappings.sort(key=lambda x: x.score, reverse=True)

        for rank, match in enumerate(mappings):

            if rank >= args.limit:
                self.pfeedback("ignoring mappings left")
                break

            winner_like = match.score == float('inf')

            output = "{c1.mptcpstreamid} <-> {c2.mptcpstreamid} with score={score} {extra}"
            formatted_output = output.format(
                c1=main_connection,
                c2=match.mapped,
                score=FG_COLORS['red'] + str(match.score) + color_off,
                extra= " <-- should be a correct match" if winner_like else ""
            )

            if match.score < args.trim:
                continue

            # match = MpTcpMapping(match.mapped, match.score, mapped_subflows)
            def _print_subflow(x):
                return "\n-" + x[0].format_mapping(x[1])
                
            
            formatted_output += ''.join( [ _print_subflow(x) for x in match.subflow_mappings])

            self.poutput(formatted_output)


    # def parser_summary():
    #     """ """
    #     pass

    summary_parser = MpTcpAnalyzerParser(description="Prints a summary of the mptcp connection")
    action_stream = summary_parser.add_argument(
        "mptcpstream", type=MpTcpStreamId, action=mp.parser.retain_stream("pcap"),
        help="mptcp.stream id")
    # TODO update the stream id autcompletion dynamically ?
    # setattr(action_stream, argparse_completer.ACTION_ARG_CHOICES, range(0, 10))

    summary_parser.add_argument(
        'destination',
        # mp.DestinationChoice,
        action="store", choices=mp.DestinationChoice, type=lambda x: mp.ConnectionRoles[x],
        help='Filter flows according to their direction'
        '(towards the client or the server)'
        'Depends on mptcpstream'
    )
    summary_parser.add_argument("--json", action="store_true", default=False,
        help="Machine readable summary.")
    @with_argparser_test(summary_parser, preload_pcap=True)
    @is_loaded
    def do_summary(self, args, unknown):
        """
        Naive summary contributions of the mptcp connection
        See summary_extended for more details
        """

        df = self.data

        # myNs = Namespace()
        # myNs._dataframes = { "pcap": self.data }
        # args = parser.parse_args(args, myNs)
        mptcpstream = args.mptcpstream

        success, ret = stats.mptcp_compute_throughput(
            self.data, args.mptcpstream, args.destination
        )
        if success is not True:
            self.perror("Throughput computation failed:")
            self.perror(ret)
            return

        if args.json:
            import json
            # TODO use self.poutput
            # or use a stream, it must just be testable
            val = json.dumps(ret, ensure_ascii=False)
            self.poutput(val)
            return

        mptcp_transferred = ret["mptcp_throughput_bytes"]
        self.poutput("mptcpstream %d transferred %d bytes." % (ret["mptcpstreamid"], mptcp_transferred))
        for tcpstream, sf_bytes in map(lambda sf: (sf["tcpstreamid"], sf["throughput_bytes"]), ret["subflow_stats"]):
            subflow_load = sf_bytes/mptcp_transferred
            self.poutput("tcpstream {} transferred {sf_tput} bytes out of {mptcp_tput}, "
                    "accounting for {tput_ratio:.2f}%".format(
                tcpstream, sf_tput=sf_bytes, mptcp_tput=mptcp_transferred, 
                tput_ratio=subflow_load*100
            ))


    parser = gen_pcap_parser({"pcap": PreprocessingActions.Preload})
    parser.description = "Export connection(s) to CSV"
    parser.epilog = '''

    '''
    # faut qu'il prenne le pcap ici sinon je ne peux pas autofiltrer :
    parser.add_argument("output", action="store", help="Output filename")

    group = parser.add_mutually_exclusive_group(required=False)
    group.add_argument('--tcpstream', action=functools.partial(FilterStream, "pcap", False), type=TcpStreamId)
    group.add_argument('--mptcpstream', action=functools.partial(FilterStream, "pcap", True), type=MpTcpStreamId)
    # parser.add_argument("protocol", action="store", choices=["mptcp", "tcp"], help="tcp.stream id visible in wireshark")
    # TODO check ?
    parser.add_argument("--destination", action="store", 
        choices=mp.DestinationChoice,
        help="tcp.stream id visible in wireshark")
    parser.add_argument("--drop-syn", action="store_true", default=False,
        help="Helper just for my very own specific usecase")
    @is_loaded
    @with_argparser(parser)
    def do_tocsv(self, args):
        """
        Selects tcp/mptcp/udp connection and exports it to csv
        """

        df = self.data
        # TODO let the parser do it
        # if args.tcpstream:
        #     # df = df[ df.tcpstream == args.tcpstream]

        #     self.poutput("Filtering tcpstream")
        #     con = TcpConnection.build_from_dataframe(df, args.tcpstream)
        #     if args.destination:
        #         self.poutput("Filtering destination")
        #         q = con.generate_direction_query(args.destination)
        #         df = df.query(q)

        # elif args.mptcpstream:
        #     self.poutput("Unsupported yet")
            # df = df[ df.mptcpstream == args.mptcpstream]

        # need to compute the destinations before dropping syn from the dataframe
        # df['tcpdest'] = np.nan;
        for streamid, subdf in df.groupby("tcpstream"):
            con = TcpConnection.build_from_dataframe(df, streamid)
            df = mpdata.tcpdest_from_connections(df, con)

            if args.drop_syn:
                # use subdf ?
                self.poutput("drop-syn Unsupported yet")
                df.drop(subdf.head(3).index, inplace=True)
                # drop 3 first packets of each connection ?
                # this should be a filter
                syns = df[df.tcpflags == mp.TcpFlags.SYN]
        #     df = df[ df.flags ]
        # if args.destination:
        #     if args.tcpstream:
                # TODO we should filter destination
        self.poutput("Writing to %s" % args.output)
        pandas_to_csv(df, args.output)


    parser = gen_bicap_parser("mptcp", True)
    parser.add_argument("--json", action="store_true", default=False,
        help="Machine readable summary.")
    parser.description = """
        Look into more details of an mptcp connection
        """
    parser.epilog = """
        summary_extended examples/client_2_redundant.pcapng 0 examples/server_2_redundant.pcapng 0
        """
    @with_argparser(parser)
    def do_summary_extended(self, args):
        """
        Summarize contributions of each subflow
        For now it is naive, does not look at retransmissions ?
        """

        print("%r" % args)
        df_pcap1 = load_into_pandas(args.pcap1, self.tshark_config)

        destinations = args.destinations
        # or list(mp.ConnectionRoles)

        for destination in destinations:
            success, basic_stats = stats.mptcp_compute_throughput(
                # TODO here we should load the pcap before hand !
                df_pcap1,
                args.pcap1stream,
                args.destinations
            )
            if success is not True:
                self.perror("Error %s" % basic_stats)

            # TODO already be done
            # TODO we should have the parser do it
            df = load_merged_streams_into_pandas(
                args.pcap1,
                args.pcap2,
                args.pcap1stream,
                args.pcap2stream,
                True,
                self.tshark_config
            )

            success, ret = stats.mptcp_compute_throughput_extended(
                df,
                stats=basic_stats,
                destination=destination
            )

            if success is not True:
                self.perror("Throughput computation failed:")
                self.perror(ret)
                return

            if args.json:
                import json
                # TODO use self.poutput
                # or use a stream, it must just be testable
                val = json.dumps(ret, ensure_ascii=False)
                self.poutput(val)
                return


            # TODO display goodput/ratio
            total_transferred = ret["mptcp_throughput_bytes"]
            #  (ret["mptcpstreamid"], ret["mptcp_bytes"]))
            msg = "mptcpstream {mptcpstreamid} throughput/goodput {mptcp_throughput_bytes}/{mptcp_goodput_bytes}"
            self.poutput(msg.format(**ret))
            for sf in ret["subflow_stats"]:

                subflow_load = sf_bytes/ret["mptcp_bytes"]
                msg = """
                tcpstream {tcpstreamid} analysis:
                - throughput: transferred {} out of {mptcp_throughput_bytes}, accounting for {.2f:throughput_contribution}%
                - goodput: transferred {mptcp_goodput} out of {mptcp_goodput_bytes}, accounting for {.2f:goodput_contribution}%
                """
                
                self.poutput(
                    msg.format(
                    mptcp_tput=ret["mptcp_throughput_bytes"],
                    **ret,
                    **sf
                ))

    # 
    @is_loaded
    @with_category(CAT_TCP)
    def do_list_tcp_connections(self, *args):
        """
        List tcp connections via their ids (tcp.stream)
        """
        streams = self.data.groupby("tcpstream")
        self.poutput('%d tcp connection(s)' % len(streams))
        for tcpstream, group in streams:
            # self.list_subflows(mptcpstream)
            self.data.tcp.connection(tcpstream)
            con = TcpConnection.build_from_dataframe(self.data, tcpstream)
            self.poutput(con)
            self.poutput("\n")


    @is_loaded
    @with_category(CAT_MPTCP)
    def do_list_mptcp_connections(self, *args):
        """
        List mptcp connections via their ids (mptcp.stream)
        """
        streams = self.data.groupby("mptcpstream")
        self.poutput('%d mptcp connection(s)' % len(streams))
        for mptcpstream, group in streams:
            self.list_subflows(mptcpstream)
            self.poutput("\n")

    # def generate_namespace(self) -> argparse.Namespace:
    #     myNamespace = Namespace()
    #     myNamespace.toto = self.data
    #     parser = argparse_completer.ACArgumentParser(
    #         description="""
    #         Mptcpanalyzer filters pcaps to keep only tcp packets.
    #         This may explain why printed packet ids dont map
    #         """
    #     )

    load_pcap1 = parser.add_argument("imported_pcap", type=str, help="Capture file to cleanup.")
    setattr(load_pcap1, argparse_completer.ACTION_ARG_CHOICES, ('path_complete', ))
    parser.add_argument("exported_pcap", type=str, help="Cleaned up file")

    @with_argparser(parser)
    def do_clean_pcap(self, args):
        """
        toto
        """
        self.poutput("Exporting a clean version of {} in {}".format(
            args.imported_pcap, args.exported_pcap))

        self.tshark_config.filter_pcap(args.imported_pcap, args.exported_pcap)


    # TODO it should be able to print for both 
    parser = gen_bicap_parser("tcp", True)
    parser.description = """This function tries merges a tcp stream from 2 pcaps
                        in an attempt to print owds. See map_tcp_connection first maybe."""

    # TODO add a limit of packets or use ppaged()
    # parser.add_argument("protocol", action="store", choices=["mptcp", "tcp"],
    #     help="tcp.stream id visible in wireshark")
    # give a choice "hash" / "stochastic"
    parser.add_argument(
        '-v', '--verbose', dest="verbose", default=False,
        action="store_true",
        help="how to display each connection"
    )
    parser.add_argument("--csv", action="store", default=None,
        help="Machine readable summary.")
    parser.epilog = '''
    You can run for example:
        map_tcp_connection examples/client_1_tcp_only.pcap examples/server_1_tcp_only.pcap  0
    '''
    @with_argparser(parser)
    @experimental
    def do_print_owds(self, args):
        """
        TODO options to diagnose errors:
        - print unmapped packets
        - print abnormal OWDs (negative etc)
        """

        self.poutput("Loading merged streams")
        df = args._dataframes["pcap"]
        result = df
        print(result.head(10))
        # print("%r" % result)
        # print(result[mpdata.TCP_DEBUG_FIELDS].head(20))
        # for key, subdf in df.groupby(_sender("tcpdest"))

        # todo sort by chronological order ?
        # for row in df.itertuples();
            # self.ppaged()

        if args.csv:
            self.pfeedback("Exporting to csv")
            with open(args.csv, "w") as fd:
                df.to_csv(
                    fd,
                    sep="|",
                    index=False,
                    header=True,
                )

        # print unmapped packets
        print("print_owds finished")
        # print("TODO display before doing plots")
        # TODO display errors
        print(result[["owd"]].head(20))
        # print(result.columns)
        mpdata.print_weird_owds(result)
        # print(result[["owd"]].head(20))

    def do_check_tshark(self, line):
        """
        Check your tshark/wireshark version
        """
        self.poutput("TODO implement automated check")
        self.poutput("you need a wireshark > 19 June 2018 with commit dac91db65e756a3198616da8cca11d66a5db6db7...")


    parser = gen_bicap_parser("mptcp", dest=True)
    parser.description = """
        Qualify reinjections of the connection.
        You might want to run map_mptcp_connection first to find out
        what map to which
        """
    parser.add_argument("--failed", action="store_true", default=False,
        help="List failed reinjections too.")
    parser.add_argument("--csv", action="store_true", default=False,
        help="Machine readable summary.")
    parser.add_argument("--debug", action="store_true", default=False,
        help="Explain decision for every reinjection.")

    @with_argparser_and_unknown_args(parser)
    @with_category(CAT_MPTCP)
    @experimental
    def do_qualify_reinjections(self, args, unknown):
        """
        test with:
            mp qualify_reinjections 0

        TODO move the code into a proper function
        """
        # TODO this should be done automatically right ?
        df_all = load_merged_streams_into_pandas(
            args.pcap1,
            args.pcap2,
            args.pcap1stream,
            args.pcap2stream,
            mptcp=True,
            tshark_config=self.tshark_config
        )

        # adds a redundant column
        df = classify_reinjections(df_all)

        # print(df_all[ pd.notnull(df_all[_sender("reinjection_of")])] [
        #     _sender(["reinjection_of", "reinjected_in", "packetid", "reltime"]) +
        #     _receiver(["packetid", "reltime"])
        # ])

        # to help debug
        # df.to_excel("temp.xls")

        def _print_reinjection_comparison(original_packet, reinj, ):
            """
            Expects tuples of original and reinjection packets
            """
            # original_packet  = sender_df.loc[ sender_df.packetid == initial_packetid, ].iloc[0]
            row = reinj

            reinjection_packetid = getattr(row, _sender("packetid")),
            reinjection_start    = getattr(row, _sender("abstime")),
            reinjection_arrival  = getattr(row, _receiver("abstime")),
            original_start       = original_packet[_sender("abstime")],
            original_arrival     = original_packet[_receiver("abstime")] 

            if reinj.redundant == False:
                # print(original_packet["packetid"])
                msg = ("packet {pktid} is a successful reinjection of {initial_packetid}."
                        " It arrived at {reinjection_arrival} to compare with {original_arrival}"
                        " while being transmitted at {reinjection_start} to compare with "
                        "{original_start}, i.e., {reinj_delta} before")
                # TODO use assert instead
                if getattr(row, _receiver("abstime")) > original_packet[ _receiver("abstime") ]:
                    print("BUG: this is not a valid reinjection after all ?")

            elif args.failed:
                # only de
                msg = "packet {pktid} is a failed reinjection of {initial_packetid}."
            else:
                return

            msg = msg.format(
                pktid               = reinjection_packetid,
                initial_packetid    = initial_packetid,

                reinjection_start   = reinjection_start,
                reinjection_arrival = reinjection_arrival,
                original_start      = original_start,
                original_arrival    = original_arrival,
                reinj_delta         = reinj.reinj_delta,
            )
            self.poutput(msg)


        # with pd.option_context('display.max_rows', None, 'display.max_columns', 300):
        #     print(reinjected_packets[["packetid", "packetid_receiver", *_receiver(["reinjected_in", "reinjection_of"])]].head())
        # TODO filter depending on --failed and --destinations

        if args.csv:
            self.pfeedback("Exporting to csv")
            # keep redundant
            # only export a subset ?
            # for 
            # df1 = df[['a','d']]
            # smalldf = df.drop()
            columns = _sender(["abstime", "reinjection_of", "reinjected_in", "packetid", "tcpstream", "mptcpstream", "tcpdest", "mptcpdest"])
            columns += _receiver(["abstime", "packetid"])
            columns += ["redundant", "owd", "reinj_delta"]

            df[columns].to_csv(
                self.stdout,
                sep="|",
                index=False,
                header=True,
            )
            return

        for destination in ConnectionRoles:

            if args.destinations and destination not in args.destinations:
                log.debug("ignoring destination %s " % destination)
                continue

            self.poutput("looking for reinjections towards mptcp %s" % destination)
            sender_df = df[df.mptcpdest == destination]
            log.debug("%d reinjections in that direction" % (len(sender_df), ))

            # TODO we now need to display successful reinjections
            reinjections = sender_df[pd.notnull(sender_df[_sender("reinjection_of")])]

            successful_reinjections = reinjections[reinjections.redundant == False]

            self.poutput("%d successful reinjections" % len(successful_reinjections))
            # print(successful_reinjections[ _sender(["packetid", "reinjection_of"]) + _receiver(["packetid"]) ])

            for row in reinjections.itertuples(index=False):

                # loc ? this is an array, sort it and take the first one ?
                initial_packetid = row.reinjection_of[0]
                # print("initial_packetid = %r %s" % (initial_packetid, type(initial_packetid)))

                original_packet  = df_all.loc[df_all.packetid == initial_packetid].iloc[0]
                # print("original packet = %r %s" % (original_packet, type(original_packet)))

                # if row.redundant == True and args.failed:
                    # _print_failed_reinjection(original_packet, row, debug=args.debug)

                _print_reinjection_comparison(original_packet, row, )

                
    parser = MpTcpAnalyzerParser(
        description="Listing reinjections of the connection"
    )
    parser.add_argument("mptcpstream", type=MpTcpStreamId, help="mptcp.stream id")
    parser.add_argument("--summary", action="store_true", default=False,
            help="Just count reinjections")

    @is_loaded
    @with_category(CAT_MPTCP)
    @with_argparser_test(parser)
    def do_list_reinjections(self, args):
        """
        List reinjections
        We want to be able to distinguish between good and bad reinjections
        (like good and bad RTOs).
        A good reinjection is a reinjection for which either:
        - the segment arrives first at the receiver
        - the cumulative DACK arrives at the sender sooner thanks to that reinjection

        To do that, we need to take into account latencies

        """

        df = self.data
        df = self.data[df.mptcpstream == args.mptcpstream]
        if df.empty:
            self.poutput("No packet with mptcp.stream == %d" % args.mptcpstream)
            return

        # known : Set[int] = set()
        # print(df.columns)

        # TODO move to outer function ?
        # TODO use ppaged
        reinjections = df.dropna(axis=0, subset=["reinjection_of"] )
        total_nb_reinjections = 0
        output = ""
        for row in reinjections.itertuples():
            # if row.packetid not in known:
            # ','.join(map(str,row.reinjection_of)
            output += ("packetid=%d (tcp.stream %d) is a reinjection of %d packet(s): " %
                (row.packetid, row.tcpstream, len(row.reinjection_of)))

            # print("reinjOf=", row.reinjection_of)
            # assuming packetid is the index
            for pktId in row.reinjection_of:
                # print("packetId %d" % pktId)
                # entry = self.data.iloc[ pktId - 1]
                entry = self.data.loc[ pktId ]
                # entry = df.loc[ df.packetid == pktId]
                # print("packetId %r" % entry)
                output += ("- packet %d (tcp.stream %d)" % (entry.packetid, entry.tcpstream))
            # known.update([row.packetid] + row.reinjection)

        self.ppaged(output)
        # reinjections = df["reinjection_of"].dropna(axis=0, )
        # print("number of reinjections of ")


    parser = MpTcpAnalyzerParser(
        description="Loads a pcap to analyze"
    )
    parser.add_argument("input_file", action=LoadSinglePcap, 
        help="Either a pcap or a csv file."
        "When a pcap is passed, mptcpanalyzer looks for a cached csv"
        "else it generates a "
        "csv from the pcap with the external tshark program.")
    @with_argparser(parser)
    def do_load_pcap(self, args):
        """
        Load the file as the current one
        """
        print(args)
        # args = shlex.split(args)
        # print(args)
        # parser = self.do_load_pcap.argparser
        # print(parser)
        # args = parser.parse_args(args)
        
        self.poutput("Loading %s" % args.input_file)
        self.data = args._dataframes["input_file"]
        self.prompt = "%s> " % os.path.basename(args.input_file)

    def do_list_available_plots(self, args):
        """
        Print available plots. Mostly for debug, you should use 'plot'.
        """
        plot_names = self.list_available_plots()
        print(plot_names)

    def list_available_plots(self):
        return self.plot_mgr.names()

    def pcap_loaded(self):
        return isinstance(self.data, pd.DataFrame)

    plot_parser = MpTcpAnalyzerParser(prog='plot', description='Generate plots')
    # TODO complete the help
    # plot throughput tcp examples/client_2_redundant.pcapng 0 examples/server_2_redundant.pcapng 0 3" "quit"
    plot_parser.epilog = '''
        You can run for example:
            plot owd tcp examples/client_2_filtered.pcapng 0 examples/server_2_filtered.pcapng 0 --display
    '''

    @with_argparser_and_unknown_args(plot_parser)
    def do_plot(self, args, unknown):
        """
        global member used by others do_plot members *
        Loads required dataframes when necessary
        """

        # Allocate plot object
        plotter = self.plot_mgr[args.plot_type].obj

        # TODO reparse with the definitive parser ?

        # 'converts' the namespace to for the syntax define a dict
        dargs = vars(args)

        print("%s" % dargs)
        dataframes = dargs.pop("_dataframes")
        # workaround argparse limitations to set as default both directions
        # TODO replace that with an action ?
        # destinations=dargs.get("destinations", list(mp.ConnectionRoles))
        # dargs.update(destinations=destinations)
        # log.debug("Selecting destinations %s" % (destinations,))
        # dataframes = plotter.preprocess(**dargs)
        print("%s" % args)
        # dataframes = args._dataframes.values()
        assert dataframes is not None, "Preprocess must return a list"
        # pass unknown_args too ?
        result = plotter.run(**dataframes, **dargs)

        # to save to file for instance
        plotter.postprocess(result, **dargs)
 
    @with_category(CAT_GENERAL)
    def do_clean_cache(self, line):
        """
        mptcpanalyzer saves pcap to csv converted files in a cache folder, (most likely
        $XDG_CACHE_HOME/mptcpanalyzer). This commands clears the cache.
        """
        cache = mp.get_cache()
        self.poutput("Cleaning cache [%s]" % cache.folder)
        cache.clean()

    def do_dump(self, args):
        """
        Dumps content of the csv file, with columns selected by the user.
        Mostly used for debug
        """
        parser = argparse.ArgumentParser(description="dumps csv content")
        parser.add_argument('columns', default=[
                            "ipsrc", "ipdst"], choices=self.data.columns, nargs="*")

        parser.add_argument('-n', default=10, action="store",
                help="Number of results to display")
        args = parser.parse_args(shlex.split(args))
        print(self.data[args.columns])

    def complete_dump(self, text, line, begidx, endidx):
        """
        Should return a list of possibilities
        """
        l = [x for x in self.data.columns if x.startswith(text)]
        return l

    # not needed in cmd2
    def do_quit(self, *args):
        """
        Quit/exit program
        """
        print("Thanks for flying with mptcpanalyzer.")
        return True

    def do_EOF(self, line):
        """
        Keep it to be able to exit with CTRL+D
        """
        return True

    def preloop(self):
        """
        Executed once when cmdloop is called
        """
        histfile = self.config["mptcpanalyzer"]['history']
        if readline and os.path.exists(histfile):
            log.debug("Loading history from %s" % histfile)
            readline.read_history_file(histfile)

    def postloop(self):
        histfile = self.config["mptcpanalyzer"]['history']
        if readline:
            log.debug("Saving history to %s" % histfile)
            readline.set_history_length(histfile_size)
            readline.write_history_file(histfile)
コード例 #8
0
ファイル: shell.py プロジェクト: nklapste/deadSFS
class DeadSFSShell(cmd2.Cmd):
    """Main shell for deadSFS"""

    intro = "Welcome to deadSFS shell. Type help or ? to list commands"
    prompt = "deadSFS>"

    CAT_CONNECTION = "Connection"
    CAT_ENCRYPTED_FTP_COMMANDS = "Encrypted FTP commands"
    CAT_RAW_FTP_COMMANDS = "Raw (non-decrypted) FTP commands"

    def _instance_pwd_file_names(self, _) -> List[str]:
        decrypted_files, failed_files = self.enc_ftp.shared_nlst()
        return decrypted_files + list(map(lambda x: "WARNING: NOT DECRYPTED: {}".format(x),  failed_files))

    filename_parser = argparse_completer.ACArgumentParser()
    filename = filename_parser.add_argument(
        "filename", nargs="?", help="decrypted filename/path")
    setattr(filename, argparse_completer.ACTION_ARG_CHOICES,
            '_instance_pwd_file_names')

    def _instance_pwd_raw_file_names(self) -> List[str]:
        return self.enc_ftp.non_decrypted_ftp.nlst()

    raw_filename_parser = argparse_completer.ACArgumentParser()
    raw_filename = raw_filename_parser.add_argument(
        "raw_filename", nargs="?", help="raw (non-decrypted) filename/path")
    setattr(raw_filename, argparse_completer.ACTION_ARG_CHOICES,
            '_instance_pwd_raw_file_names')

    def __init__(self, enc_key: bytes, tls: bool = False,
                 certfile=None, keyfile=None, cafile=None):
        """Initialize the deadSFS shell

        :param enc_key: The private key used for filename and file encryption
        :param tls: Enable using a FTP TLS connection

        The below parameters are only relevant if ``tls`` is equal
        to :obj:`True`:

        :param certfile: Path to the clients *.pem self-certificate
        :param keyfile: Path to the clients *.pem self-certificate key
        :param cafile: Path the *.pem certificate authority to validate the
            FTP server's connection
        """
        self.allow_cli_args = False
        super().__init__()
        if tls:
            context = ssl.SSLContext(ssl.PROTOCOL_TLS)

            # use TLS_V1_2 only instead of TLS_V1_1, TLS_V1, SSLv2, or SSLv3
            context.options |= ssl.OP_NO_TLSv1
            context.options |= ssl.OP_NO_TLSv1_1
            context.options |= ssl.OP_NO_SSLv2
            context.options |= ssl.OP_NO_SSLv3

            # use a custom self certificate,otherwise, use default certificates
            if certfile and keyfile:
                context.load_cert_chain(certfile=certfile, keyfile=keyfile)
            else:
                context.load_default_certs()

            # enable validating the FTP servers connection with a ca
            if cafile:
                context.load_verify_locations(cafile=cafile)

            self.enc_ftp = EncryptedFTPTLS(enc_key, context=context)
        else:
            self.enc_ftp = EncryptedFTP(enc_key)

    def do_quit(self, _):
        """Exit out of the deadSFS shell

        Close connections for the FTP server if they exist.
        """
        self.enc_ftp.close()
        return super().do_quit(_)

    connect_argparser = argparse.ArgumentParser()
    connect_argparser.add_argument('host', type=str,
                                   help="hostname to connect to")
    connect_argparser.add_argument('port', type=int,
                                   help="port to connect to")

    @with_category(CAT_CONNECTION)
    @with_argparser(connect_argparser)
    def do_connect(self, args):
        """Connect and login into the remote FTP server"""
        print(self.enc_ftp.connect(args.host, args.port))
        username = input("username: "******".")

    @ftp_connected
    @with_category(CAT_CONNECTION)
    def do_disconnect(self, _):
        """Disconnect from the remote FTP server"""
        print(self.enc_ftp.quit())

    @staticmethod
    def _fix_filename_arg(filename_arg: str) -> str:
        if filename_arg is None or \
                isinstance(filename_arg, str) and not filename_arg.strip():
            return ""
        return filename_arg

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_nlst(self, args):
        """List the contents of the current working directory of the
        remote filesystem"""
        filename = DeadSFSShell._fix_filename_arg(args.filename)
        print(self.enc_ftp.nlst(filename))

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_raw_nlst(self, args):
        """List the contents of the directory specified by its encrypted
        filename on the remote filesystem"""
        filename = DeadSFSShell._fix_filename_arg(args.raw_filename)
        print(self.enc_ftp.non_decrypted_ftp.nlst(filename))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_mkd(self, args):
        """Make a sub-directory within the current working directory
        of the remote filesystem"""
        print(self.enc_ftp.mkd(args.filename))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_rmd(self, args):
        """Remove a directory from the remote filesystem"""
        print(self.enc_ftp.rmd(args.filename))

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_raw_rmd(self, args):
        """Remove a directory specified by its encrypted filename
        from the remote filesystem"""
        print(self.enc_ftp.non_decrypted_ftp.rmd(args.raw_filename))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    def do_pwd(self, _):
        """Print the pathname of the current directory on the server"""
        print(self.enc_ftp.pwd())

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    def do_raw_pwd(self, _):
        """Print the non-decrypted pathname of the current directory
        on the server"""
        print(self.enc_ftp.non_decrypted_ftp.pwd())

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_cwd(self, args):
        """Change the current working directory of the remote filesystem"""
        filename = DeadSFSShell._fix_filename_arg(args.filename)
        print(self.enc_ftp.cwd(filename))

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_raw_cwd(self, args):
        """Change the current working directory of the remote filesystem
        to the one specified by its encrypted path"""
        filename = DeadSFSShell._fix_filename_arg(args.raw_filename)
        print(self.enc_ftp.non_decrypted_ftp.cwd(filename))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    def do_wf(self, arg):
        """Encrypt and write a file into the remote filesystem"""
        with open(arg, "r") as f:
            print(self.enc_ftp.storefile(arg, f.read()))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_rf(self, args):
        """Decrypt, Read, and save in the current working directory a file
        from the remote filesystem"""
        content = self.enc_ftp.readfile(args.filename)
        print("obtained {}'s content:\n{}".format(args.filename, content))
        with open(args.filename, "w") as f:
            f.write(content)

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_raw_rf(self, args):
        """Read, and save in the current working directory a file
        from the remote filesystem"""
        cmd = "RETR {}".format(args.raw_filename)
        buf = BytesIO()

        def callback(data: bytes):
            buf.write(data)

        self.enc_ftp.non_decrypted_ftp.retrbinary(cmd, callback)
        buf.seek(0)
        content = buf.read().decode("utf-8")
        print("obtained {}'s content:\n{}".format(args.raw_filename, content))
        with open(args.raw_filename, "w") as f:
            f.write(content)

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(filename_parser)
    def do_rmf(self, args):
        """Delete a file from the remote filesystem"""
        print(self.enc_ftp.delete(args.filename))

    @ftp_connected
    @with_category(CAT_RAW_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_raw_rmf(self, args):
        """Delete a file specified by its encrypted filename from the
        remote filesystem without"""
        print(self.enc_ftp.non_decrypted_ftp.delete(args.raw_filename))

    # we have to re-build this manually as we cannot copy argparsers
    # this is a direct limitation of python currently
    rename_filename_parser = argparse_completer.ACArgumentParser()
    rename_filename = rename_filename_parser.add_argument(
        "filename", nargs="?", help="decrypted filename to change")
    setattr(rename_filename, argparse_completer.ACTION_ARG_CHOICES,
            '_instance_pwd_file_names')
    rename_filename_parser.add_argument("new_filename",
                                        help="new decrypted filename")

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser_and_unknown_args(rename_filename_parser)
    def do_rename(self, args):
        """Rename an encrypted file"""
        print(self.enc_ftp.rename(args.filename, args.new_filename))

    @ftp_connected
    @with_category(CAT_ENCRYPTED_FTP_COMMANDS)
    @with_argparser(raw_filename_parser)
    def do_vald(self, args):
        """Validate that all files (including files within nested directories)
        within a specified directory are properly encrypted and not wrongly
        modified on the remote FTP server"""
        filename = DeadSFSShell._fix_filename_arg(args.raw_filename)
        self.enc_ftp.validate_dir(filename)
コード例 #9
0
ファイル: analyse.py プロジェクト: kin9-0rz/saam
class CmdLineApp(Cmd):

    sdcard = '/storage/sdcard0/'
    maps = None

    def __init__(self, file_path):
        Cmd.__init__(self)
        self.prompt = 'saam > '

        self.shortcuts.remove(('@', 'load'))
        self.shortcuts.append(('@', 'adb_cmd'))
        self.shortcuts.remove(('@@', '_relative_load'))
        self.shortcuts.append(('$', 'adb_shell_cmd'))
        self.shortcuts.append(('qc', 'quit_and_clean'))

        self.apk_path = file_path
        self.apk = APK(self.apk_path)
        self.apk_out = os.path.basename(file_path) + ".out"
        self.smali_files = None
        self.smali_dir = None
        self.adb = None
        self.smali_method_descs = []
        self.java_files = []

        vsmali_action = CmdLineApp.vsmali_parser.add_argument(
            'sfile', help='smali file path')
        setattr(vsmali_action, argparse_completer.ACTION_ARG_CHOICES,
                ('delimiter_complete', {
                    'delimiter': '/',
                    'match_against': self.smali_method_descs
                }))

        vjava_action = CmdLineApp.vjava_parser.add_argument(
            'jfile', help='java file path')
        setattr(vjava_action, argparse_completer.ACTION_ARG_CHOICES,
                ('delimiter_complete', {
                    'delimiter': '/',
                    'match_against': self.java_files
                }))

    def do_quit_and_clean(self, arg):
        import shutil
        shutil.rmtree(self.apk_out)

        self._should_quit = True
        return self._STOP_AND_EXIT

    # ------------------- Hardware And System  ---------------------
    def do_devinfos(self, arg):
        '''
        显示设备硬件信息
        '''
        cmd = 'getprop ro.product.brand'
        print('Brand  :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.product.model'
        print('Model  :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.product.device'
        print('Device :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.product.cpu.abi'
        print('CPU    :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop persist.radio.imei'
        print('IMEI   :', self.adb.run_shell_cmd(cmd).decode())

    def do_osinfos(self):
        '''显示设备系统信息'''
        cmd = 'getprop ro.build.description'
        print('Build Desc    :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.build.date'
        print('Build Data    :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.build.version.release'
        print('Build Version :', self.adb.run_shell_cmd(cmd)[:-1].decode())
        cmd = 'getprop ro.build.version.sdk'
        print('SDK Version   :', self.adb.run_shell_cmd(cmd)[:-1].decode())

    # ---------------------- Manifest -------------------------
    @staticmethod
    def serialize_xml(org_xml):
        import xmlformatter
        import xml
        # org_xml = re.sub(r'>[^<]+<', '><', org_xml)

        try:
            formatter = xmlformatter.Formatter()
            return formatter.format_string(org_xml).decode('utf-8')
        except xml.parsers.expat.ExpatError:
            return org_xml

    def do_manifest(self, arg):
        '''显示清单信息'''
        # TODO 清单的显示形式更改
        # 1. 分类显示,简单,默认
        # 2. 原始XML的方式,有可能显示超过页面,需要使用more的方式。
        org_xml = self.apk.get_org_manifest()
        if org_xml:
            print(self.serialize_xml(org_xml))

    def get_package(self):
        return self.apk.get_manifest()['@package']

    def get_main_activity(self):
        try:
            activities = self.apk.get_manifest()['application']['activity']
        except KeyError:
            return None

        if not isinstance(activities, list):
            activities = [activities]

        for item in activities:
            content = json.dumps(item)
            if 'android.intent.action.MAIN' in content\
                    and 'android.intent.category.LAUNCHER' in content:
                return item['@android:name']

        return None

    def do_receiver(self, arg):
        try:
            receivers = self.apk.get_manifest()['application']['receiver']
        except KeyError:
            return None
        print(json.dumps(receivers, indent=4, sort_keys=True))

    def do_activity(self, arg):
        try:
            receivers = self.apk.get_manifest()['application']['activity']
        except KeyError:
            return None
        print(json.dumps(receivers, indent=4, sort_keys=True))

    def do_service(self, arg):
        try:
            receivers = self.apk.get_manifest()['application']['service']
        except KeyError:
            return None
        print(json.dumps(receivers, indent=4, sort_keys=True))

    def show_risk_children(self, flag=False):
        result = ''
        pflag = True
        self.apk.get_files().sort(key=lambda k: (k.get('type'), k.get('name')))
        for item in self.apk.get_files():
            if flag:
                print(item.get('type'), item.get('name'))
                continue

            if item.get('type') not in ['dex', 'apk', 'elf']:
                continue

            if pflag:
                result = Color.red('\n===== Risk Files =====\n')
                pflag = False

            result += item.get('type') + ' ' + item.get('name') + '\n'

        return result

    children_parser = argparse.ArgumentParser()
    children_parser.add_argument('-a', '--all', action='store_true')

    @with_argparser(children_parser)
    def do_children(self, args):
        '''
        列出APK中的特殊文件
        '''
        self.apk.get_files().sort(key=lambda k: (k.get('type'), k.get('name')))
        for item in self.apk.get_files():
            if args.all:
                print(item.get('type'), item.get('name'))
                continue

            if item.get('type') in ['dex', 'apk', 'elf']:
                print(item.get('type'), item.get('name'))

    # ------------------- Static Analysis -------------------------
    # TODO 默认使用了apktool反编译
    # 转化为jar文件/默认解压为.class文件,cfr反编译工具会自行处理。
    def do_decompile(self, arg):
        '''
        使用apktool反编译apk, 默认初始化清单相关的包。

        '''
        pkgs = self.find_pkg_refx_manifest(self.apk.get_org_manifest())

        apktool.decode(None, self.apk_out, self.apk_path, True)
        for item in os.listdir(self.apk_out):
            if not item.startswith('smali'):
                continue

            self.smali_dir = SmaliDir(os.path.join(self.apk_out, item),
                                      include=pkgs)

    @staticmethod
    def find_pkg_refx_manifest(manifest):
        '''
        找出与清单相关的包
        '''
        if not manifest:
            return
        pkgs = set()
        ptn_s = r'android:name="([^\.]*?\.[^\.]*?)\..*?"'
        ptn = re.compile(ptn_s)

        for item in ptn.finditer(manifest):
            pkgs.add(item.groups()[0])

        if "android.intent" in pkgs:
            pkgs.remove("android.intent")
        if "android.permission" in pkgs:
            pkgs.remove("android.permission")

        return pkgs

    init_smali_argparser = argparse.ArgumentParser()
    init_smali_argparser.add_argument('-m',
                                      '--manifest',
                                      action='store_true',
                                      help='根据manifest相关包初始化')
    init_smali_argparser.add_argument('-f',
                                      '--filter',
                                      action='store_true',
                                      help='根据过滤列表初始化')

    @with_argparser(init_smali_argparser)
    def do_init_smali(self, args):
        '''
        初始化smali,默认初始化所有

        '''
        pkgs = None

        if args.manifest:
            pkgs = self.find_pkg_refx_manifest(self.apk.get_org_manifest())

        self.smali_dir = None
        for item in os.listdir(self.apk_out):
            if not item.startswith('smali'):
                continue

            sd = SmaliDir(os.path.join(self.apk_out, item), include=pkgs)
            if self.smali_dir:
                self.smali_dir[len(self.smali_dir):] = sd
            else:
                self.smali_dir = sd
                # print('初始化{0}个smali文件'.format(len(self.smali_dir)))

        for items in self.smali_dir:
            for mtd in items.get_methods():
                self.smali_method_descs.append(mtd.get_desc())

        print('初始化{0}个smali文件'.format(len(self.smali_dir)))

    def do_build(self, arg):
        '''
        使用apktool回编译apk
        '''
        apktool.build(self.apk_out, force=True)

    def __init__smali_dir(self):
        pass

    def show_risk_perm(self):
        result = ''
        if not self.apk.get_manifest():
            return result

        risk_perms = ['_SMS', '_CALL', '_DEVICE_ADMIN', '_AUDIO', '_CONTACTS']
        ps = set()
        pflag = True

        def process_perm_item(item, pflagx=pflag):

            for perm in risk_perms:
                if perm not in item.get('@android:name'):
                    continue

                if pflagx:
                    result += Color.red('===== Risk Permissions =====\n')
                    pflagx = False

                name = item.get('@android:name')
                if name in ps:
                    continue
                result += name + '\n'
                ps.add(name)

        perms = self.apk.get_manifest().get('uses-permission', [])

        if isinstance(perms, dict):
            process_perm_item(perms)
        else:
            for item in self.apk.get_manifest().get('uses-permission', []):
                process_perm_item(item)

        app = self.apk.get_manifest().get('application')
        if app is None:
            return result

        recs = app.get('receiver', None)

        def process_item(item):
            text = ''
            perm = item.get('@android:permission', '')
            if '_DEVICE' not in perm:
                return text
            if pflag:
                text += Color.red('===== Risk Permissions =====\n')
            text += perm + item.get('@android:name') + '\n'

        if isinstance(recs, dict):
            text = process_item(item)
            if text:
                result += text
                pflag = False
        elif isinstance(recs, list):
            for item in recs:
                text = process_item(item)
                if text:
                    result += text
                    pflag = False
                    break

        if not pflag:
            result += '\n'

        return result

    def show_risk_code(self, level):
        result = Color.red('===== Risk Codes =====\n')
        for k, v in RISKS.items():
            if v['lvl'] < level:
                continue
            kflag = True
            for sf in self.smali_dir:
                for mtd in sf.get_methods():
                    mflag = True
                    lines = re.split(r'\n\s*', mtd.get_body())
                    for idx, line in enumerate(lines):
                        for ptn in v['code']:
                            if re.search(ptn, line):
                                if kflag:
                                    result += Color.magenta('---' + k +
                                                            '---\n')
                                    kflag = False
                                if mflag:
                                    result += Color.green(' ' +
                                                          str(mtd)) + '\n'
                                    mflag = False
                                result += ' ' * 3 + str(idx) + line + '\n'
                                break

        return result

    risk_parser = argparse.ArgumentParser()
    risk_parser.add_argument('-l',
                             '--level',
                             help='指定风险级别,0/1/2/3,0为最低级别,3为最高级别')
    risk_parser.add_argument('-f',
                             '--force',
                             action='store_true',
                             help='强制覆盖已有文件')

    @with_argparser(risk_parser)
    def do_risk(self, args):
        if os.path.exists(self.apk_path + '.risk.txt'):
            if not args.force:
                # TODO read file
                return
        text = ''
        text += self.show_risk_perm()

        level = 3
        if args.level:
            level = int(args.level)

        text += self.show_risk_code(level)
        text += self.show_risk_children()
        # TODO save to file
        self.ppaged(text)
        # so
        # 文本
        # 二进制文件
        # 图片

    def ref(self, desc):
        global notes
        global classes
        global recursion_times
        recursion_times += 1

        if recursion_times > recursion_limit:
            return

        body = None
        for smali_file in self.smali_dir:
            for mtd in smali_file.get_methods():
                if desc in str(mtd):
                    body = mtd.get_body()
                    break
        print(desc)

        if body:
            for smali_file in self.smali_dir:
                for mtd in smali_file.get_methods():
                    if desc in mtd.get_body():
                        if desc.startswith('L'):
                            tmp = desc[:desc.index(';')]
                            classes.add(tmp[:tmp.rindex('/')])
                        tmp = str(mtd)[:str(mtd).index(';')]
                        classes.add(tmp[:tmp.rindex('/')])
                        notes.append([desc, str(mtd)])
                        self.ref(str(mtd))

    def refs(self, desc):
        self.ref(desc)

    def do_xrefs(self, arg):
        pass

    ref_parser = argparse.ArgumentParser()
    ref_parser.add_argument('-l', '--limit', help='递归次数')

    @with_argparser(ref_parser)
    def do_ref(self, args):
        if len(args) != 1:
            return

        if args.limit:
            recursion_limit = args.l

        mtd = args[0]
        self.refs(mtd)

        dot = Digraph()
        if notes:
            dot.edges(notes)
            dot.render('refs.gv', view=True)

    def main_ref(self, arg):
        '''
            生成入口函数调用数,调用层数默认100。
            可以传入一个数值修改递归次数。
        '''
        args = arg.split()
        if len(args) == 1:
            times = int(args[0])
            global recursion_limit
            recursion_limit = times
        else:
            recursion_limit = 100

        dot = Digraph()
        dot.attr('node', style='filled', fillcolor='red')

        # global recursion_times
        # recursion_times = 0
        # if not self.smali_files:
        #     self.smali_files = parse_smali(self.apk_out + os.sep + 'smali')

        main_acitivity = self.get_main_activity()
        main_acitivity = main_acitivity.replace('.', '/') + ';->onCreate'
        self.refs(main_acitivity)
        dot.node(main_acitivity)

        # recs = self.apk.get_receivers()
        # for item in recs:
        #     dot.node(item)
        #     self.refs(item)

        # dot.attr('node', shape='box', style='filled',
        #          fillcolor='white', color='black')

        # if notes:
        #     dot.edges(notes)
        #     dot.render('refs.gv', view=True)

        # for item in sorted(list(classes)):
        #     print(item)

    search_parser = argparse.ArgumentParser()
    search_parser.add_argument('txt', help='默认在Dex中搜索字符串')
    # <public type="string" name="failed_text" id="0x7f050002" />
    search_parser.add_argument('-r',
                               '--res',
                               action='store_true',
                               help='查找资源(id/name/图片名)')
    search_parser.add_argument('-a',
                               '--all',
                               action='store_true',
                               help='在所有文本文件中查找(不包含Dex)')

    @with_argparser(search_parser)
    def do_search(self, args):
        def find_in_the_layout_xml(txt):
            results = []
            layout_path = os.path.join(self.apk_out, 'res', 'layout')
            for root, _, names in os.walk(layout_path):
                for name in names:
                    xml_path = os.path.join(root, name)
                    with open(xml_path, mode='r') as f:
                        lines = f.readlines()
                        for line in lines:
                            if txt in line:
                                results.append(os.path.splitext(name)[0])
                                break
            return results

        def find_in_the_smali_dir(txt):
            for smali_file in self.smali_dir:
                if txt in smali_file.get_content():
                    print(smali_file)

        def find_in_the_txt(content):
            self.apk.get_files().sort(
                key=lambda k: (k.get('type'), k.get('name')))
            for item in self.apk.get_files():
                if item.get('type') != 'txt':
                    continue
                if 'META-INF' in item.get('name'):
                    continue
                txt_path = os.path.join(self.apk_out, item.get('name'))
                with open(txt_path, mode='r') as f:
                    if 'txt_path' in f.read():
                        print(item.get('name'))

        if args.res:
            public_xml = os.path.join(self.apk_out, 'res', 'values',
                                      'public.xml')
            rtype = None
            rname = None
            with open(public_xml, mode='r') as f:
                lines = f.readlines()
                flag = False
                for line in lines:
                    if args.txt in line:
                        match = line.strip()
                        print(match)
                        g = re.search(r'type="(.*?)" name="(.*?)"',
                                      match).groups()
                        if g:
                            rtype = g[0]
                            rname = g[1]
                        break
                else:
                    return

            if rtype in {'string', 'layout'}:
                find_in_the_smali_dir(rname)
            elif rtype in {'id', 'drawable'}:
                txts = find_in_the_layout_xml(rname)
                for txt in txts:
                    print(os.path.join('res', 'layout', txt + '.xml'))
                    find_in_the_smali_dir(txt)
                    print()
        elif args.all:
            find_in_the_txt(args.txt)
        else:
            find_in_the_smali_dir(args.txt)

    vjava_parser = argparse_completer.ACArgumentParser(prog='vjava')

    def do_dex2java(self, args):
        """将APK转化为Java代码,使用vjava可以查看。
        """
        from .decompiler.enjarify import dex2jar
        output = os.path.join(self.apk_out, 'classes.jar')
        dex2jar(self.apk_path, output=output)

        from .decompiler.cfr import class2java
        java_output = os.path.join(self.apk_out, 'java')
        print(java_output)
        class2java(output, java_output)

    def do_init_java(self, args):
        java_output = os.path.join(self.apk_out, 'java')
        for root, dirs, files in os.walk(java_output):
            for d in dirs:
                for f in files:
                    self.java_files.append(os.path.join(root, d, f))

    vjava_parser = argparse_completer.ACArgumentParser(prog='vjava')

    @cmd2.with_argparser(vjava_parser)
    def do_vjava(self, args):
        """查看java代码,可以指定类、方法

        Args:
            args (TYPE): 类、方法
        """
        # TODO 封装cfr
        # TODO 修改apktutils的反编译接口
        if not self.java_files:
            print('please dex2jar and init java')
            return

        try:
            with open(args.jfile) as f:
                print(f.read())
        except Exception as e:
            pass

    vsmali_parser = argparse_completer.ACArgumentParser(prog='vsmali')

    # @cmd2.with_category('CAT_AUTOCOMPLETE')
    @cmd2.with_argparser(vsmali_parser)
    def do_vsmali(self, args):
        """查看smali代码,可以指定类、方法
        Args:
            args (TYPE): 类、方法(自动生成)
        """
        # TODO 判断smali目录是否为空
        # 如果为空,直接提示,请反编译
        if not self.smali_dir:
            print('please decompile and init smali')
            return

        try:
            print(self.smali_dir.get_method_from_desc(args.sfile).get_body())
        except Exception as e:
            pass

        # for item in self.smali_dir:
        #     print(dir(item))
        #     print(item.get_file_path(), item.get_class(), item.get_methods())
        # for item in self.smali_files:
        # print(item)
        #

    # ------------------- ADB -------------------------
    adb_parser = argparse.ArgumentParser()
    adb_parser.add_argument(
        '-s',
        '--serial',
        help='use device with given serial (overrides $ANDROID_SERIAL)')

    @with_argparser(adb_parser)
    def do_adb_ready(self, args):
        '''
        连接设备/模拟器,准备adb命令。
        '''
        serial = None
        if args.serial:
            serial = args.serial

        self.adb = pyadb3.ADB(device=serial)
        if len(self.adb.get_output().decode()) > 10:
            print('ADB ready.')
        else:
            print("unable to connect to device.")

    def do_adb(self, arg):
        '''
        执行adb命令
        '''
        if not self.adb:
            self.adb = pyadb3.ADB()
        self.adb.run_cmd(arg)
        print(self.adb.get_output().decode('utf-8', errors='ignore'))

    def do_adb_shell(self, arg):
        '''
        执行adb shell命令
        '''
        if not self.adb:
            self.adb = pyadb3.ADB()

        self.adb.run_shell_cmd(arg)
        print(self.adb.get_output().decode('utf-8', errors='ignore'))

    def do_topactivity(self, args):
        if not self.adb:
            self.adb = pyadb3.ADB()

        self.adb.run_shell_cmd(
            "dumpsys activity activities | grep mFocusedActivity")
        print(self.adb.get_output().decode('utf-8',
                                           errors='ignore').split()[-2])

    def do_details(self, args):
        if not self.adb:
            self.adb = pyadb3.ADB()
        self.adb.run_shell_cmd("dumpsys package {}".format(self.get_package()))
        print(self.adb.get_output().decode('utf-8', errors='ignore'))

    # ------------------- 备份还原 -------------------------

    # ------------------- 应用管理 -------------------------
    lspkgs_parser = argparse.ArgumentParser()
    lspkgs_parser.add_argument('-f',
                               '--file',
                               action='store_true',
                               help='显示应用关联的 apk 文件')
    lspkgs_parser.add_argument('-d',
                               '--disabled',
                               action='store_true',
                               help='只显示 disabled 的应用')
    lspkgs_parser.add_argument('-e',
                               '--enabled',
                               action='store_true',
                               help='只显示 enabled 的应用')
    lspkgs_parser.add_argument('-s',
                               '--system',
                               action='store_true',
                               help='只显示系统应用')
    lspkgs_parser.add_argument('-3',
                               '--three',
                               action='store_true',
                               help='只显示第三方应用')
    lspkgs_parser.add_argument('-i',
                               '--installer',
                               action='store_true',
                               help='显示应用的 installer')
    lspkgs_parser.add_argument('-u',
                               '--uninstall',
                               action='store_true',
                               help='包含已卸载应用')
    lspkgs_parser.add_argument('filter',
                               type=str,
                               nargs="?",
                               help='包名包含 < FILTER > 字符串')

    @with_argparser(lspkgs_parser)
    def do_lspkgs(self, args):
        '''
        查看应用列表,默认所有应用
        '''
        cmd = 'pm list packages'
        if args.file:
            cmd += ' -f'

        if args.disabled:
            cmd += ' -d'
        elif args.enabled:
            cmd += ' -e'

        if args.system:
            cmd += ' -s'
        elif args.three:
            cmd += ' -3'

        if args.installer:
            cmd += ' -i'
        elif args.uninstall:
            cmd += ' -u'

        if args.filter:
            cmd += ' ' + args.filter

        self.adb.run_shell_cmd(cmd)
        print(self.adb.get_output().decode())

    def do_install(self, arg):
        '''
        安装应用到手机或模拟器
        '''
        self.adb.run_cmd('install -r -f {}'.format(self.apk_path))
        output = self.adb.get_output().decode()

        if self.adb.get_error():
            print(self.adb.get_error().decode(errors='ignore'))
        elif 'Failure' in output:
            print(output)
        else:
            # TODO if the sdcard path doesn't exist.
            cmd = 'touch %s.now' % self.sdcard
            self.adb.run_shell_cmd(cmd)

    def do_uninstall(self, arg):
        '''
        卸载应用
        '''
        self.adb.run_cmd('uninstall %s' % self.get_package())
        self.clearsd()

    def clearsd(self):
        ''' pull the newer files from sdcard.
        '''
        cmd = 'find %s -path "%slost+found" -prune -o -type d -print -newer %s.now -delete' % (
            self.sdcard, self.sdcard, self.sdcard)
        self.adb.run_shell_cmd(cmd)

    startapp_parser = argparse.ArgumentParser()
    startapp_parser.add_argument('-d', '--debug', action='store_true')

    def do_strace(self, args):
        '''
        使用 strace 跟踪应用
        setenforce 0  # In Android 4.3 and later, if SELinux is enabled, strace will fail with "strace: wait: Permission denied"

        有两种方式:
        1. 使用调试的方式启动应用,strace -p $pid
        2. trace -p $zygote_pid,启动应用
        '''
        # cmd = "strace -f -p `ps | grep zygote | awk '{print $2}'`"
        print(self.sdcard)
        cmd = "set `ps | grep zygote`; strace -p $2 -f -tt -T -s 500 -o {}strace.txt".format(
            self.sdcard)
        self.adb.run_shell_cmd(cmd)

    @with_argparser(startapp_parser)
    def do_startapp(self, args):
        '''启动应用'''
        main_acitivity = self.get_main_activity()
        if not main_acitivity:
            print("It does not have main activity.")
            return

        cmd = 'am start -n %s/%s' % (self.get_package(), main_acitivity)
        if args.debug:
            cmd = 'am start -D -n %s/%s' % (self.get_package(), main_acitivity)

        self.adb.run_shell_cmd(cmd)

    def do_stopapp(self, arg):
        '''停止应用'''
        cmd = 'am force-stop %s' % self.get_package()
        self.adb.run_shell_cmd(cmd)

    kill_parser = argparse.ArgumentParser()
    kill_parser.add_argument('-a', '--all', action='store_true')

    @with_argparser(kill_parser)
    def do_kill(self, args):
        '''杀死应用'''
        cmd = 'am kill %s' % self.get_package()
        if args.all:
            cmd = 'am kill-all'

        self.adb.run_shell_cmd(cmd)

    def do_clear(self, args):
        cmd = 'pm clear {}'.format(self.get_package())
        self.adb.run_shell_cmd(cmd)

    def do_screencap(self, args):
        import time
        cmd = 'screencap -p /sdcard/{}.png'.format(time.time())
        if not self.adb:
            self.adb = pyadb3.ADB()

        self.adb.run_shell_cmd(cmd)

    monkey_parser = argparse.ArgumentParser()
    monkey_parser.add_argument('-v', '--verbose', action='store_true')
    monkey_parser.add_argument('count', type=int, help="Count")

    @with_argparser(monkey_parser)
    def do_monkey(self, args):
        if not self.adb:
            self.adb = pyadb3.ADB()

        cmd = "monkey -p {} ".format(self.get_package())
        if args.verbose:
            cmd += '-v '

        cmd += str(args.count)

        self.adb.run_shell_cmd(cmd)
        print(self.adb.get_output().decode())

    # --------------------------------------------------------

    def do_set_sdcard(self, arg):
        '''
        设置sdcard位置
        '''
        if len(arg.split()) != 1:
            print('Please give one argument to set sdcard path.')
            return
        self.sdcard = arg

    # @options([make_option('-d', '--debug', action='store_true'),
    #           make_option('-e', '--edit', action='store_true')])
    def do_test(self, args):
        '''
        测试应用(未支持)
        '''
        print(args)
        print(''.join(args))
        if args.debug:
            print('debug')

        # TODO 增加自动化测试
        # 获取Receiver, 启动
        # 获取Service,启动
        # 获取Acitivity,启动

    def do_pids(self, arg):
        '''
        显示应用进程
        '''
        print(self.adb.run_shell_cmd('ps | grep %s' % self.get_package()))

    def lsof(self):
        axml = self.apk.get_manifest()
        if axml:
            lines = self.adb.run_shell_cmd('ps | grep %s' %
                                           axml.getPackageName()).decode()
            if not lines:
                return
            pids = []
            for line in lines.strip().split('\r\r\n'):
                pids.append(line.split()[1])

            for pid in pids:
                self.adb.run_shell_cmd('lsof | grep %s' % pid)
                lines = self.adb.get_output().decode().split('\r\r\n').decode()
                for line in lines:
                    # print(line)
                    if not line.endswith('(deleted)'):
                        continue

                    tmps = line.split()
                    fdno = tmps[3]
                    if not fdno.isdecimal():
                        continue

                    print(line)
                    filename = tmps[8].replace('/', '_')
                    print('%s %s' % (fdno, filename))
                    self.adb.run_shell_cmd(
                        "cat /proc/%s/fd/%s > /storage/sdcard0/%s" %
                        (pid, fdno, filename))
                    self.adb.run_cmd('pull -a -p /storage/sdcard0/%s' %
                                     filename)

    def do_lssd(self, arg):
        '''列出SDCARD新增的文件'''
        command = ('find /storage/sdcard0 -path "/storage/sdcard0/lost+found"'
                   ' -prune -o -type f -print -newer /storage/sdcard0/.now')
        self.adb.run_shell_cmd(command)
        print(self.adb.get_output().decode())

    def pulldata(self):
        '''
            pull /data/data/pkg
        '''
        pkg = self.get_package()
        self.adb.run_shell_cmd('cp -r /data/data/%s /storage/sdcard0' % pkg)
        # self.adb.run_cmd('pull -a -p /storage/sdcard0/%s %s' % (pkg, pkg))
        # self.adb.run_shell_cmd('rm -r /storage/sdcard0/%s' % pkg)

    def pullsd(self):
        '''
            pull the newer files from sdcard.
        '''
        command = ('find /storage/sdcard0 -path "/storage/sdcard0/lost+found"'
                   ' -prune -o -type f -print -newer /storage/sdcard0/.now')
        ret = self.adb.run_shell_cmd(command).decode()

        dir_set = set([])
        import os
        for line in ret.split('\r\r\n'):
            if line == '/storage/sdcard0/.now':
                continue
            if line:
                print('->', line)
                path = os.path.dirname(line)
                flag = 0
                skip_path = None
                for item in dir_set:
                    if item == path:
                        flag == 2
                        break

                    if item in path:
                        flag = 2
                        break

                    if path in item:
                        flag = 1
                        skip_path = item
                        break

                if flag == 1:
                    print(path, skip_path)
                    dir_set.add(path)
                    dir_set.remove(skip_path)

                elif flag == 0:
                    dir_set.add(path)

        for line in dir_set:
            print(line, )
            local_path = os.path.dirname(line)[1:]
            if not os.path.exists(local_path):
                os.makedirs(local_path)
            self.adb.run_cmd('pull -a %s %s' % (line, local_path))

    def do_pull(self, arg):
        '''导出样本的所有的运行生成的文件'''
        self.pulldata()
        self.pullsd()

    # ----------------------------- 内存操作 -----------------------------
    # 内存字符串查看?内存字符串修改?
    def do_memview(self, arg):
        '''查看内存分布'''
        if not self.maps:
            self.get_maps()

        print(self.maps)

    def get_maps(self):
        pkg = self.get_package()
        lines = self.adb.run_shell_cmd('ps | grep %s' % pkg).decode()
        pids = []
        for line in lines.strip().split('\r\r\n'):
            pids.append(line.split()[1])

        for pid in pids:
            lines = self.adb.run_shell_cmd('ls /proc/%s/task/' % pid)
            clone = lines.decode().split()[-1]
            cmd = 'cat /proc/%s/maps' % clone
            self.adb.run_shell_cmd(cmd).decode()
            self.maps = self.adb.get_output().decode()

    def memdump(self, arg):
        '''
        内存Dump(仍未支持,需要gdb)

        用法:
        memdump 内存起始地址 内存结束地址
        '''
        pass