Ejemplo n.º 1
0
    def jql_search(self, *args):
        """Submit a JQL search query to the server"""
        jql = None
        if args and args != ('', ):
            jql = ' '.join(args)
            SAVED_QUERIES.add(jql=jql)
        else:
            entry_type = ih.make_selections(
                ['Select a saved query', 'Type a JQL query'],
                prompt='Choose one',
                unbuffered=True)
            if 'Select a saved query' in entry_type:
                selected_query = ih.make_selections(
                    SAVED_QUERIES.find(get_fields='name,jql'),
                    prompt='Choose one',
                    item_format='({name}) {jql}',
                    unbuffered=True)
                if selected_query:
                    jql = selected_query[0]['jql']
                    name = selected_query[0]['name']
                    _id = selected_query[0]['_id']
                    update_kwargs = {'chosen_on': dh.utc_now_float_string()}
                    if name is None:
                        name = ih.user_input('Enter a name for this selection')
                        if name:
                            update_kwargs.update(name=name)
                    SAVED_QUERIES.update(_id, **update_kwargs)
                else:
                    jql = ih.user_input('Enter your JQL query')
                    if not jql:
                        return
                    SAVED_QUERIES.add(jql=jql)
            else:
                jql = ih.user_input('Enter your JQL query')
                if not jql:
                    return
                SAVED_QUERIES.add(jql=jql)

        self._info['last_jql'] = jql

        if not 'order by' in jql.lower() and self._info['orderby_fields']:
            jql += ' ORDER BY ' + ', '.join(self._info['orderby_fields'])

        results = jql_search(jql,
                             session=self._session,
                             count=self._info['count_only'],
                             fields=self._info['return_fields'],
                             return_raw=self._info['raw_json'])
        pprint(results)
        print('\njql:', jql)
Ejemplo n.º 2
0
def prompt_for_new_branch_name(name=''):
    """Prompt user for the name of a new allowed branch name

    - name: if provided, verify that it is an acceptable new branch name and
      prompt if it is invalid

    Branch name is not allowed to have the name of any QA_BRANCHES as a prefix
    """
    QA_BRANCHES = _get_repo_settings('QA_BRANCHES')
    NON_SELECTABLE_BRANCHES = _get_repo_settings('NON_SELECTABLE_BRANCHES')
    RX_QA_PREFIX = _get_repo_settings('RX_QA_PREFIX')
    remote_branches = get_remote_branches()
    local_branches = get_local_branches()
    while True:
        if not name:
            name = ih.user_input('Enter name of new branch to create')
        if not name:
            break
        if name in remote_branches:
            print('{} already exists on remote server'.format(repr(name)))
            name = ''
        elif name in local_branches:
            print('{} already exists locally'.format(repr(name)))
            name = ''
        elif name in NON_SELECTABLE_BRANCHES:
            print('{} is not allowed'.format(repr(name)))
            name = ''
        elif RX_QA_PREFIX.match(name):
            print('{} not allowed to use any of these as prefix: {}'.format(
                repr(name), repr(QA_BRANCHES)))
            name = ''
        else:
            break
    return name.replace(' ', '_')
Ejemplo n.º 3
0
def _settings_for_docker_ok(exception=False):
    """Return True if settings.ini has the required values set

    - exception: if True, raise an exception if settings are not ok (after
      optional sync attempt)

    If any are missing, prompt to sync settings with vimdiff
    """
    global SETTINGS
    settings_keys_for_docker = [
        'container_name', 'image_version', 'port', 'rm', 'aof',
        'redis_data_dir'
    ]
    missing_settings = set(settings_keys_for_docker) - set(SETTINGS.keys())
    if missing_settings != set():
        message = 'Update your settings.ini to have: {}'.format(
            sorted(list(missing_settings)))
        print(message)
        resp = ih.user_input('Sync settings.ini with vimdiff? (y/n)')
        if resp.lower().startswith('y'):
            sh.sync_settings_file(__name__)
            SETTINGS = sh.get_all_settings(__name__).get(sh.APP_ENV, {})
            missing_settings = set(settings_keys_for_docker) - set(
                SETTINGS.keys())
            if missing_settings == set():
                return True
            elif exception:
                message = 'Update your settings.ini to have: {}'.format(
                    sorted(list(missing_settings)))
                raise Exception(message)
        else:
            if exception:
                raise Exception(message)
    else:
        return True
Ejemplo n.º 4
0
def tag_release(auto=False):
    """Select a recent remote commit on TAG_BRANCH to tag

    - auto: if True, create tag on last commit and generate message

    Return True if tag was successful
    """
    TAG_BRANCH = _get_repo_settings('TAG_BRANCH')
    success = update_branch(TAG_BRANCH)
    if not success:
        return
    tag = dh.local_now_string('%Y-%m%d-%H%M%S')
    if not auto:
        print('\nRecent commits')
        commit_id = select_commit_to_tag()
        if not commit_id:
            return
        summary = ih.user_input('One-line summary for tag')
        if not summary:
            summary = tag
    else:
        commit_id = get_last_commit_id()
        summary = tag
    commits = get_commits_since_last_tag(until=commit_id)
    if not commits:
        return
    notes_file = '/tmp/{}.txt'.format(tag)
    with open(notes_file, 'w') as fp:
        fp.write('{}\n\n'.format(summary))
        fp.write('\n'.join(commits) + '\n')

    cmd = 'git tag -a {} {} -F {}'.format(tag, commit_id, repr(notes_file))
    if not auto:
        bh.run('vim {}'.format(notes_file))
        print('Tag command would be -> {}'.format(cmd))
        resp = ih.user_input('Continue? (y/n)')
        if not resp.lower().startswith('y'):
            return

    ret_code = bh.run(cmd, show=True)
    if ret_code != 0:
        return

    return bh.run('git push --tags', show=True)
Ejemplo n.º 5
0
def merge_qa_to_source(qa='', auto=False):
    """Merge the QA-verified code to SOURCE_BRANCH and delete merged branch(es)

    - qa: name of qa branch to merge to source
    - auto: if True, don't ask if everything looks ok

    Return qa name if merge(s) and delete(s) were successful
    """
    QA_BRANCHES = _get_repo_settings('QA_BRANCHES')
    SOURCE_BRANCH = _get_repo_settings('SOURCE_BRANCH')
    LOCAL_BRANCH = _get_repo_settings('LOCAL_BRANCH')
    if qa not in QA_BRANCHES:
        show_qa(all_qa=True)
        print()
        qa = select_qa(full_only=True)
    if not qa:
        return
    env_branches = get_qa_env_branches(qa, display=True)
    if not env_branches:
        print('Nothing on {} to merge...'.format(qa))
        return

    print()
    if not auto:
        resp = ih.user_input('Does this look correct? (y/n)')
        if not resp.lower().startswith('y'):
            print('\nNot going to do anything')
            return

    most_recent = env_branches[0]
    delete_after_merge = most_recent['contains'][:]
    delete_after_merge.extend([b['branch'] for b in env_branches])

    success = merge_branches_locally(SOURCE_BRANCH, source=qa)
    if not success:
        print('\nThere was a failure, not going to delete these: {}'.format(
            repr(delete_after_merge)))
        return

    cmd = 'git push -uf origin {}:{}'.format(LOCAL_BRANCH, SOURCE_BRANCH)
    ret_code = bh.run(cmd, show=True)
    if ret_code != 0:
        print('\nThere was a failure, not going to delete these: {}'.format(
            repr(delete_after_merge)))
        return

    delete_after_merge.extend(get_merged_remote_branches())
    success = delete_remote_branches(*delete_after_merge)
    if success:
        return qa
Ejemplo n.º 6
0
def force_push_local(qa='', *branches, to_source=False):
    """Do a git push -f of LOCAL_BRANCH to specified qa branch or SOURCE_BRANCH

    - qa: name of qa branch to push to
    - branches: list of remote branch names that were merged into LOCAL_BRANCH
    - to_source: if True, force push to SOURCE_BRANCH (only allowed if func
      that called it is in FUNCS_ALLOWED_TO_FORCE_PUSH_TO_SOURCE)

    Return True if push was successful

    Only allowed to be called from funcs in FUNCS_ALLOWED_TO_FORCE_PUSH (because
    these are functions that just finished creating a clean LOCAL_BRANCH from the
    remote SOURCE_BRANCH, with other remote branches combined in (via rebase or
    merge)
    """
    caller = inspect.stack()[1][3]
    assert caller in FUNCS_ALLOWED_TO_FORCE_PUSH, (
        'Only allowed to invoke force_push_local func from {}... not {}'.
        format(repr(FUNCS_ALLOWED_TO_FORCE_PUSH), repr(caller)))
    SOURCE_BRANCH = _get_repo_settings('SOURCE_BRANCH')
    if to_source:
        assert caller in FUNCS_ALLOWED_TO_FORCE_PUSH_TO_SOURCE, (
            'Only allowed to force push to {} when invoked from {}... not {}'.
            format(SOURCE_BRANCH, repr(FUNCS_ALLOWED_TO_FORCE_PUSH_TO_SOURCE),
                   repr(caller)))
    QA_BRANCHES = _get_repo_settings('QA_BRANCHES')
    LOCAL_BRANCH = _get_repo_settings('LOCAL_BRANCH')
    current_branch = get_branch_name()
    if current_branch != LOCAL_BRANCH:
        print('Will not do a force push with branch {}, only {}'.format(
            repr(current_branch), repr(LOCAL_BRANCH)))
        return
    if qa not in QA_BRANCHES:
        print('Branch {} is not one of {}'.format(repr(qa), repr(QA_BRANCHES)))
        return

    env_branches = get_qa_env_branches(qa, display=True)
    if env_branches:
        print()
        resp = ih.user_input('Something is already there, are you sure? (y/n)')
        if not resp.lower().startswith('y'):
            return

    ret_codes = []
    combined_name = qa + '--with--' + '--'.join(branches)
    cmd_part = 'git push -uf origin {}:'.format(LOCAL_BRANCH)
    ret_codes.append(bh.run(cmd_part + qa, show=True))
    ret_codes.append(bh.run(cmd_part + combined_name, show=True))
    if all([x == 0 for x in ret_codes]):
        return True
Ejemplo n.º 7
0
def main(query):
    """Pass a search query to duckduckgo api"""
    query = query or ih.user_input('duckduckgo query')
    if not query:
        return

    selected = ih.make_selections(
        ph.duckduckgo_api(query),
        wrap=True,
        item_format='{text}',
    )
    if selected:
        ph.logger.info('Selected {}'.format(' '.join(
            [x['link'] for x in selected])))
        with open(ph.LOGFILE, 'a') as fp:
            pprint(selected, fp)
Ejemplo n.º 8
0
def main(query):
    """Pass a search query to youtube"""
    query = query or ih.user_input('youtube query')
    if not query:
        return

    selected = ih.make_selections(
        ph.youtube_serp(query),
        wrap=False,
        item_format='{duration} .::. {title} .::. {user} .::. {uploaded}',
    )
    if selected:
        ph.logger.info('Selected {}'.format(' '.join(
            [x['link'] for x in selected])))
        with open(ph.LOGFILE, 'a') as fp:
            pprint(selected, fp)
Ejemplo n.º 9
0
    def edit_comment_timestamp_select():
        """Use select_comments to select a mark/comment for the current file

        Prompt for a new timestamp (usually +/-1 from current timestamp)
        """
        selected = select_comments(prompt='Select mark/comment to edit timestamp')
        if selected:
            prompt = 'Enter new timestamp (old was {})'.format(selected[0].get('timestamp', ''))
            timestamp = ih.user_input(prompt)
            if timestamp:
                try:
                    timestamp = int(timestamp)
                except ValueError:
                    print('{} is not an integer'.format(timestamp))
                else:
                    _id = selected[0]['_id']
                    COMMENTS.update(_id, timestamp=timestamp)
        else:
            print()
Ejemplo n.º 10
0
def clear_qa(*qas, all_qa=False, force=False):
    """Clear whatever is on selected QA branches

    - qas: names of qa branches that may have things pushed to them
        - if no qas passed in, you will be prompted to select multiple
    - all_qa: if True and no qa passed in, clear all qa branches
    - force: if True, delete the specified qa branches without prompting
      for confirmation

    Return True if deleting branch(es) was successful
    """
    QA_BRANCHES = _get_repo_settings('QA_BRANCHES')
    if not all_qa:
        valid = set(QA_BRANCHES).intersection(set(qas))
        if valid == set():
            qas = select_qa_with_times(multi=True)
            if not qas:
                return
            qas = [b['branch'] for b in qas]
        else:
            qas = list(valid)
    else:
        qas = QA_BRANCHES

    parts = []
    for qa in qas:
        parts.append('^{}$|^{}--'.format(qa, qa))
    branches = get_remote_branches(grep='|'.join(parts), all_branches=True)

    if not branches:
        return
    if not force:
        print('\n', branches, '\n')
        resp = ih.user_input('Does this look correct? (y/n)')
        if not resp.lower().startswith('y'):
            print('\nNot going to do anything')
            return

    return delete_remote_branches(*branches)
Ejemplo n.º 11
0
def main(query, **kwargs):
    """Pass a search query to google"""
    query = query or ih.user_input('google query')
    if not query:
        return

    session = ph.new_requests_session()
    selected = ih.make_selections(
        ph.google_serp(query, session=session, **kwargs),
        wrap=False,
        item_format='{title} .::. {link}',
    )
    for item in selected:
        remote_basename = os.path.basename(item['link'])
        ext = remote_basename.split('.')[-1]
        localfile = ''
        if ext != remote_basename:
            localfile = lazy_filename(item['title']) + '.' + ext
        else:
            localfile = lazy_filename(
                item['link'].split('://')[-1].strip('/').replace('/', '--')
            ) + '.html'
        ph.download_file(item['link'], localfile, session=session)
Ejemplo n.º 12
0
    def delete(basename=None):
        """Delete audio file and remove related data from COMMENTS

        In FILES, the item will be updated to 'audio=False' in case there is a
        related video file of the same name (which this will not delete)
        """
        if basename is None:
            basename = get_current_basename()
            if not basename:
                return
        paths = get_paths_from_basenames(basename)
        if paths:
            files = moc.find_audio(*paths)
            if files:
                file_id = FILES[basename].get('_id')
                comment_ids = [
                    x['_id']
                    for x in COMMENTS.find('basename:{}'.format(basename), get_fields='_id')
                ]
                print('For {}, found {} file(s) and {} comment(s)'.format(
                    repr(basename),
                    len(files),
                    len(comment_ids)
                ))
                yn = ih.user_input('\nAre you sure you want to delete? (y/n)')
                if yn.lower().startswith('y'):
                    for f in files:
                        try:
                            remove(f.strip('\'"'))
                        except Exception as e:
                            logger.error('Could not delete {}... {}'.format(repr(f), repr(e)))
                        else:
                            logger.info('Deleted {}'.format(repr(f)))
                    if file_id:
                        FILES.update(file_id, audio=False)
                    if comment_ids:
                        COMMENTS.delete_many(*comment_ids)
Ejemplo n.º 13
0
def choose_old_selection_or_make_new_selection(field_type):
    """Choose from old groups of selections for field_type or make a new selection

    - field_type: one of the keys in ALLOWED_FIELD_TYPE_INFO dict
    """
    assert field_type in ALLOWED_FIELD_TYPE_INFO, (
        'field_type {} is not one of {}'.format(
            repr(field_type), repr(sorted(ALLOWED_FIELD_TYPE_INFO.keys()))))

    selections = None
    choice = ih.make_selections(
        ['Choose from old selections', 'Make a new selection'],
        prompt='Choose one for {} fields in search results'.format(field_type),
        unbuffered=True)
    if 'Choose from old selections' in choice:
        found = SELECTED_FIELDS.find('type:{}'.format(field_type),
                                     get_fields='selected,name')

        selected = ih.make_selections(found,
                                      item_format='({name}): {selected}',
                                      prompt='Choose one',
                                      wrap=False,
                                      unbuffered=True)
        if selected:
            selections = selected[0]['selected']
            name = selected[0]['name']
            _id = selected[0]['_id']
            update_kwargs = {'chosen_on': dh.utc_now_float_string()}
            if name is None:
                name = ih.user_input('Enter a name for this selection')
                if name:
                    update_kwargs.update(name=name)
            SELECTED_FIELDS.update(_id, **update_kwargs)
    else:
        selections = get_last_or_make_selection(field_type, force_new=True)

    return selections
Ejemplo n.º 14
0
            CharacterController.move_right(self._character_name,
                                           ih.from_string(n))

        def up(self, n):
            """Move up n units"""
            CharacterController.move_up(self._character_name,
                                        ih.from_string(n))

        def down(self, n):
            """Move down n units"""
            CharacterController.move_down(self._character_name,
                                          ih.from_string(n))


if GameLoop:
    display_name = ih.user_input('\nWhat is the character name')
    character_name = display_name.replace(' ', '_').lower()

    # Keyboard shortcuts that work when the gameloop is running
    chfunc = {
        'h': (partial(CharacterController.move_left, character_name,
                      2), 'move left 2 units'),
        'l': (partial(CharacterController.move_right, character_name,
                      2), 'move right 2 units'),
        'k': (partial(CharacterController.move_up, character_name,
                      2), 'move up 2 units'),
        'j': (partial(CharacterController.move_down, character_name,
                      2), 'move down 2 units'),
        'H': (partial(CharacterController.move_left, character_name,
                      5), 'move left 5 units'),
        'L': (partial(CharacterController.move_right, character_name,
Ejemplo n.º 15
0
def main(url_or_file):
    """Create a soup object from a url or file and explore with ipython"""
    url_or_file = url_or_file or ih.user_input('url')
    ph.soup_explore(url_or_file)
Ejemplo n.º 16
0
def main(**kwargs):
    """For matching instances, issue a command on the instance via SSH"""
    ec2 = ah.EC2(kwargs['profile'])
    find = kwargs['find']
    command = kwargs['command']
    use_private_ip = kwargs['private_ip']
    matched_instances = []
    local_pems = ah.find_all_pems()

    if not command:
        if kwargs['non_interactive']:
            print('No command specified')
            return
        else:
            command = ih.user_input('Enter remote command')
            if not command:
                print('No command specified')
                resp = ih.user_input(
                    'Do you want interactive SSH session(s)? (y/n)')
                if resp.lower().startswith('y') is False:
                    return

    if ah.AWS_EC2 is not None:
        ec2.update_collection()
        running_instances = ah.AWS_EC2.find(
            'status:running',
            get_fields='name, status, pem, id, ip, ip_private, sshuser',
            include_meta=False,
            limit=ah.AWS_EC2.size)
    else:
        instances = ec2.get_all_instances_filtered_data()
        running_instances = [
            ih.rename_keys(
                ih.filter_keys(
                    instance,
                    'Tags__Value, State__Name, KeyName, InstanceId, PublicIpAddress, PrivateIpAddress'
                ), **INSTANCE_KEY_NAME_MAPPING)
            for instance in ih.find_items(instances, 'State__Name:running')
        ]

    for term in ih.string_to_list(find):
        for item in ih.find_items(running_instances, 'name:${}'.format(term)):
            if item not in matched_instances:
                matched_instances.append(item)
        for item in ih.find_items(running_instances, 'id:${}'.format(term)):
            if item not in matched_instances:
                matched_instances.append(item)
        for item in ih.find_items(running_instances, 'ip:${}'.format(term)):
            if item not in matched_instances:
                matched_instances.append(item)
        for item in ih.find_items(running_instances,
                                  'ip_private:${}'.format(term)):
            if item not in matched_instances:
                matched_instances.append(item)
    if not find:
        matched_instances = running_instances

    ih.sort_by_keys(matched_instances, 'name, ip')

    # # Uncomment and modify if you ever need to use a .pem file that
    # # is different than what AWS thinks it should be
    # for i, instance in enumerate(matched_instances):
    #     if instance.get('pem', '') == 'old-pem':
    #         matched_instances[i]['pem'] = 'new-pem'

    if kwargs['non_interactive'] is False:
        matched_instances = ih.make_selections(
            matched_instances,
            prompt='Select instances',
            item_format='{id} ({name}) at {ip} ({ip_private}) using {pem}',
            wrap=False)

    for instance in matched_instances:
        pem_name = instance['pem']
        ip = instance['ip'] if not use_private_ip else instance['ip_private']
        if not ip:
            continue
        pem_file = local_pems.get(pem_name)
        if not pem_file:
            print('Could not find {} pem in ~/.ssh for {}.'.format(
                repr(pem_name), repr(instance)))
            continue

        sshuser = instance.get('sshuser')
        if not sshuser:
            sshuser = ah.determine_ssh_user(ip, pem_file)
        if not sshuser and kwargs['verbose']:
            print('--------------------------------------------------')
            print('\nCould not determine SSH user for {}'.format(
                repr(instance)))
            continue
        else:
            if ah.AWS_EC2:
                hash_id = ah.AWS_EC2.get_hash_id_for_unique_value(
                    instance['id'])
                ah.AWS_EC2.update(hash_id, sshuser=sshuser)

        if kwargs['verbose']:
            print('--------------------------------------------------')
            print('\nInstance {} ({}) at {} with pem {} and user {}\n'.format(
                instance['id'], instance['name'], ip, pem_name, sshuser))

        ah.do_ssh(ip, pem_file, sshuser, command, kwargs['timeout'],
                  kwargs['verbose'])
Ejemplo n.º 17
0
    def __call__(self):
        print(self._startup_message)
        self._char_hist = []
        self._cmd_hist = []
        while True:
            click.secho(self._prompt, nl=False, fg='cyan', bold=True)
            try:
                ch = click.getchar()
                self._char_hist.append(ch)
            except (EOFError, KeyboardInterrupt):
                break
            else:
                if ch in ['\x03', '\x04']:
                    break

            if ch == '?':
                try:
                    if self._char_hist[-2] == '?':
                        self.docstrings()
                        self.shortcuts()
                    else:
                        print('?\n', self._class_doc())
                        print(self._startup_message)
                except IndexError:
                    print('?\n', self._class_doc())
                    print(self._startup_message)
            elif ch == '-':
                try:
                    if self._pre_input_hook:
                        pre_input_data = self._pre_input_hook()
                    else:
                        pre_input_data = {}
                    user_input = ih.user_input_fancy('', '- ')
                    if self._post_input_hook:
                        post_input_data = self._post_input_hook()
                    else:
                        post_input_data = {}
                    if self._input_hook:
                        bh.call_func(
                            self._input_hook,
                            **user_input,
                            **pre_input_data,
                            **post_input_data,
                            logger=logger
                        )
                    else:
                        self._collection.add(
                            cmd='-',
                            user_input=user_input['text'],
                            status='ok',
                            **pre_input_data,
                            **post_input_data
                        )
                except click.exceptions.Abort:
                    print()
                    continue
            elif ch == ':':
                try:
                    user_input = click.prompt(text='', prompt_suffix=':', default='', show_default=False)
                except click.exceptions.Abort:
                    print()
                    continue
                else:
                    if not user_input:
                        print()
                        continue
                cmd = user_input.split()[0]
                args = user_input.split()[1:]
                self._cmd_hist.append(cmd)

                if cmd == 'pdb':
                    import pdb; pdb.set_trace()
                    continue

                if cmd == 'ipython':
                    ih.start_ipython(warn=True, self=self)
                    continue

                try:
                    cmd_func = getattr(self, cmd)
                except AttributeError:
                    self._collection.add(cmd=cmd, status='error', error_type='invalid command')
                    logger.error('invalid command: {}'.format(cmd))
                    result = self._wishlist.find(
                        'cmd:{}'.format(cmd),
                        admin_fmt=True,
                        item_format='[NOT FULFILLED YET] {message} ({_ts})'
                    )
                    if result:
                        print(result[0])
                    else:
                        message = ih.user_input('what do you wish this command did')
                        if message:
                            self._wishlist.add(cmd=cmd, message=message)
                    continue

                info = bh.call_func(cmd_func, *args, logger=logger)
                info['cmd'] = cmd
                if cmd in self._DONT_LOG_CMDS:
                    info = {}

                if info:
                    self._collection.add(**info)
            elif ch in self._chfunc_dict:
                print(ch)
                bh.call_func(self._chfunc_dict[ch][0], logger=logger)
                if ch in self._break_chars:
                    break
            elif ch in self._break_chars:
                print(ch)
                break
            else:
                try:
                    print(repr(ch), ord(ch))
                except TypeError:
                    # ord() expected a character, but string of length 2 found
                    #   - happens if you press 'Esc' before another key
                    print(repr(ch))
                result = self._wishlist.find(
                    'ch:{}'.format(ch),
                    admin_fmt=True,
                    item_format='[NOT FULFILLED YET] {message} ({_ts})'
                )
                if result:
                    print(result[0])
                else:
                    try:
                        if ch == self._char_hist[-2]:
                            message = ih.user_input('what do you wish this key press did')
                            if message:
                                self._wishlist.add(ch=ch, message=message)
                    except IndexError:
                        pass