Пример #1
0
def run(wf, argv):
    """Run ``searchio user`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    query = wf.decode(args.get('<query>') or '').strip()
    ICON_BACK = ctx.icon('back')
    # log.debug('args=%r', args)

    f = util.FileFinder([ctx.searches_dir], ['json'])
    searches = [Search.from_file(p) for p in f]
    searches.sort(key=attrgetter('title'))

    if query:
        searches = wf.filter(query, searches, key=attrgetter('title'))
    else:
        it = wf.add_item(
            u'Configuration',
            'Back to configuration',
            arg='back',
            valid=True,
            icon=ICON_BACK)
        it.setvar('action', 'back')

    log.debug('%d search(es)', len(searches))

    if args.get('--text') or util.textmode():  # Display for terminal

        print(_text_help, file=sys.stderr)

        table = util.Table([u'ID', u'Title'])
        for s in searches:
            table.add_row((s.uid, s.title))

        print(table)
        print()

    else:  # Display for Alfred
        for s in searches:
            it = wf.add_item(
                s.title,
                'Reveal configuration in Finder',
                autocomplete=s.title,
                arg=s.uid,
                valid=True,
                icon=s.icon,
            )
            it.setvar('search', s.uid)
            it.setvar('action', 'reveal')
            m = it.add_modifier('cmd', u'Delete search')
            m.setvar('action', 'delete')

        wf.send_feedback()
Пример #2
0
def run(wf, argv):
    """Run ``searchio search`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    query = wf.decode(args.get('<query>') or '').strip()
    uid = wf.decode(args.get('<search>') or '').strip()
    if not uid or not query:
        raise RuntimeError('<search> and <query> are required')

    start = time()
    p = ctx.search(uid)
    if not os.path.exists(p):
        raise ValueError('Unknown search "{}" ({!r})'.format(uid, p))

    search = engines.Search.from_file(p)

    results = cached_search(ctx, search, query)

    log.debug('[search/%s] %d result(s) in %0.3fs',
              uid, len(results), time() - start)

    # ---------------------------------------------------------
    # Text results

    if args.get('--text') or util.textmode():
        print()
        msg = u'{:d} result(s) for "{:s}"'.format(len(results), query)
        print(msg, file=sys.stderr)
        print('=' * len(msg))
        print()
        table = util.Table([u'Suggestion', u'URL'])
        for r in results:
            table.add_row(r[:2])
            # print('{0}\t{1}'.format(term, url))
        print(table)
        print()

    # ---------------------------------------------------------
    # Alfred results

    else:
        for r in results:
            wf.add_item(
                r.term,
                u'Search {} for "{}"'.format(r.source, r.term),
                arg=r.url,
                autocomplete=r.term + u' ',
                valid=True,
                icon=search.icon,
            )

        wf.send_feedback()
Пример #3
0
def do_get_url(wf, args):
    """Check clipboard and browsers for a URL."""
    ctx = Context(wf)
    ICON_CLIPBOARD = ctx.icon('clipboard')

    items = []
    url = clipboard_url()
    if url:
        items.append(
            dict(title='Clipboard',
                 subtitle=url,
                 autocomplete=url,
                 icon=ICON_CLIPBOARD))

    # Browsers
    cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', JXA_RUNNING] + \
        BROWSERS.keys()
    output = subprocess.check_output(cmd)
    running = json.loads(output)

    for name in BROWSERS:
        app, script = BROWSERS[name]
        if not running[name]:
            log.debug('"%s" not running', name)
            continue
        log.debug('fetching current tab from "%s" ...', name)

        script = script % dict(appname=name)
        cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script]
        output = subprocess.check_output(cmd)
        data = json.loads(output)
        if not data:
            log.debug('no URL for "%s"', name)
            continue
        title = data['title'] or 'Untitled'
        url = data['url']
        items.append(
            dict(title=title,
                 subtitle=url,
                 autocomplete=url,
                 icon=app,
                 icontype='fileicon'))

    if not items:
        wf.add_item('No Clipboard or Browser URL',
                    'Enter a URL or copy one to clipboard.',
                    icon=ICON_WARNING)

    for item in items:
        wf.add_item(**item)

    wf.send_feedback()
Пример #4
0
def run(wf, argv):
    """Run ``searchio search`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    query = wf.decode(args.get('<query>') or '').strip()
    uid = wf.decode(args.get('<search>') or '').strip()
    if not uid or not query:
        raise RuntimeError('<search> and <query> are required')

    start = time()
    p = ctx.search(uid)
    if not os.path.exists(p):
        raise ValueError('Unknown search "{}" ({!r})'.format(uid, p))

    search = engines.Search.from_file(p)

    results = cached_search(ctx, search, query)

    log.debug('[search/%s] %d result(s) in %0.3fs', uid, len(results),
              time() - start)

    # ---------------------------------------------------------
    # Text results

    if args.get('--text') or util.textmode():
        print()
        msg = u'{:d} result(s) for "{:s}"'.format(len(results), query)
        print(msg, file=sys.stderr)
        print('=' * len(msg))
        print()
        table = util.Table([u'Suggestion', u'URL'])
        for r in results:
            table.add_row(r[:2])
            # print('{0}\t{1}'.format(term, url))
        print(table)
        print()

    # ---------------------------------------------------------
    # Alfred results

    else:
        for r in results:
            wf.add_item(
                r.term,
                u'Search {} for "{}"'.format(r.source, r.term),
                arg=r.url,
                autocomplete=r.term + u' ',
                valid=True,
                icon=search.icon,
            )

        wf.send_feedback()
Пример #5
0
def do_import_search(wf, url):
    """Parse URL for OpenSearch config."""
    ctx = Context(wf)
    # ICON_IMPORT = ctx.icon('import')
    ICONS_PROGRESS = [
        ctx.icon('progress-1'),
        ctx.icon('progress-2'),
        ctx.icon('progress-3'),
        ctx.icon('progress-4'),
    ]

    data = wf.cached_data('import', None, max_age=0, session=True)
    if data:
        error = data['error']
        search = data['search']
        # Clear cache data
        wf.cache_data('import', None, session=True)
        wf.cache_data('import-status', None, session=True)

        if error:
            wf.add_item(error, icon=ICON_ERROR)
            wf.send_feedback()
            return

        it = wf.add_item(u'Add "{}"'.format(search['name']),
                         u'↩ to add search',
                         valid=True,
                         icon=search['icon'])

        for k, v in search.items():
            it.setvar(k, v)

    else:
        progress = int(os.getenv('progress') or '0')
        i = progress % len(ICONS_PROGRESS)
        picon = ICONS_PROGRESS[i]
        log.debug('progress=%d, i=%d, picon=%s', progress, i, picon)
        wf.setvar('progress', progress + 1)
        if not is_running('import'):
            run_in_background('import', ['./searchio', 'fetch', url])

        status = wf.cached_data('import-status', None, max_age=0, session=True)
        title = status or u'Fetching OpenSearch Configuration …'

        wf.rerun = 0.2
        wf.add_item(title,
                    u'Results will be shown momentarily',
                    icon=picon)

    wf.send_feedback()
Пример #6
0
def run(wf, argv):
    """Run ``searchio list`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    query = wf.decode(args.get('<query>') or '').strip()
    ICON_BACK = ctx.icon('back')

    engs = engines.load(*ctx.engine_dirs)
    # engs = engines.engines()

    if query:
        engs = wf.filter(query, engs, key=attrgetter('title'))
    else:
        it = wf.add_item(
            u'Configuration',
            'Back to configuration',
            arg='back',
            valid=True,
            icon=ICON_BACK)
        it.setvar('action', 'back')

    if args.get('--text') or util.textmode():  # Display for terminal

        print(_text_help, file=sys.stderr)

        table = util.Table([u'ID', u'Name', u'Description', u'Variants'])
        for e in engs:
            n = '{:>8}'.format(len(e.variants))
            table.add_row((e.uid, e.title, e.description, n))

        print(table)
        print()

    else:  # Display for Alfred
        for e in engs:
            title = u'{} …'.format(e.title)
            subtitle = (str(len(e.variants)) + ' variant' +
                        ('s', '')[len(e.variants) == 1])
            it = wf.add_item(
                title,
                subtitle,
                autocomplete=e.title,
                arg=e.uid,
                valid=True,
                icon=ctx.icon(e.uid))
            it.setvar('engine', e.uid)
            it.setvar('action', 'searches')

        wf.send_feedback()
Пример #7
0
def do_toggle_alfred_sorts(wf):
    """Toggle "Alfred sorts results" setting."""
    ctx = Context(wf)
    v = ctx.getbool('ALFRED_SORTS_RESULTS')
    if v:
        new = '0'
        status = 'off'
    else:
        new = '1'
        status = 'on'

    log.debug('turning "ALFRED_SORTS_RESULTS" %s ...', status)
    set_config('ALFRED_SORTS_RESULTS', new)

    print(Variables(title='Alfred sorts results', text='Turned ' + status))
Пример #8
0
def do_toggle_show_query(wf):
    """Toggle "show query in results" setting."""
    ctx = Context(wf)
    v = ctx.getbool('SHOW_QUERY_IN_RESULTS')
    if v:
        new = '0'
        status = 'off'
    else:
        new = '1'
        status = 'on'

    log.debug('turning "SHOW_QUERY_IN_RESULTS" %s ...', status)
    set_config('SHOW_QUERY_IN_RESULTS', new)

    print(Variables(title='Show query in results', text='Turned ' + status))
Пример #9
0
def do_toggle_alfred_sorts(wf):
    """Toggle "Alfred sorts results" setting."""
    ctx = Context(wf)
    v = ctx.getbool('ALFRED_SORTS_RESULTS')
    if v:
        new = '0'
        status = 'off'
    else:
        new = '1'
        status = 'on'

    log.debug('turning "ALFRED_SORTS_RESULTS" %s ...', status)
    set_config('ALFRED_SORTS_RESULTS', new)

    print(Variables(title='Alfred sorts results', text='Turned ' + status))
Пример #10
0
def do_toggle_show_query(wf):
    """Toggle "show query in results" setting."""
    ctx = Context(wf)
    v = ctx.getbool('SHOW_QUERY_IN_RESULTS')
    if v:
        new = '0'
        status = 'off'
    else:
        new = '1'
        status = 'on'

    log.debug('turning "SHOW_QUERY_IN_RESULTS" %s ...', status)
    set_config('SHOW_QUERY_IN_RESULTS', new)

    print(Variables(title='Show query in results', text='Turned ' + status))
Пример #11
0
def do_import_search(wf, url):
    """Parse URL for OpenSearch config."""
    ctx = Context(wf)
    # ICON_IMPORT = ctx.icon('import')
    ICONS_PROGRESS = [
        ctx.icon('progress-1'),
        ctx.icon('progress-2'),
        ctx.icon('progress-3'),
        ctx.icon('progress-4'),
    ]

    data = wf.cached_data('import', None, max_age=0, session=True)
    if data:
        error = data['error']
        search = data['search']
        # Clear cache data
        wf.cache_data('import', None, session=True)
        wf.cache_data('import-status', None, session=True)

        if error:
            wf.add_item(error, icon=ICON_ERROR)
            wf.send_feedback()
            return

        it = wf.add_item(u'Add "{}"'.format(search['name']),
                         u'↩ to add search',
                         valid=True,
                         icon=search['icon'])

        for k, v in search.items():
            it.setvar(k, v)

    else:
        progress = int(os.getenv('progress') or '0')
        i = progress % len(ICONS_PROGRESS)
        picon = ICONS_PROGRESS[i]
        log.debug('progress=%d, i=%d, picon=%s', progress, i, picon)
        wf.setvar('progress', progress + 1)
        if not is_running('import'):
            run_in_background('import', ['./searchio', 'fetch', url])

        status = wf.cached_data('import-status', None, max_age=0, session=True)
        title = status or u'Fetching OpenSearch Configuration …'

        wf.rerun = 0.2
        wf.add_item(title, u'Results will be shown momentarily', icon=picon)

    wf.send_feedback()
Пример #12
0
def do_get_url(wf, args):
    """Check clipboard and browsers for a URL."""
    ctx = Context(wf)
    ICON_CLIPBOARD = ctx.icon('clipboard')

    items = []
    url = clipboard_url()
    if url:
        items.append(dict(title='Clipboard', subtitle=url, autocomplete=url,
                          icon=ICON_CLIPBOARD))

    # Browsers
    cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', JXA_RUNNING] + \
        BROWSERS.keys()
    output = subprocess.check_output(cmd)
    running = json.loads(output)

    for name in BROWSERS:
        app, script = BROWSERS[name]
        if not running[name]:
            log.debug('"%s" not running', name)
            continue
        log.debug('fetching current tab from "%s" ...', name)

        script = script % dict(appname=name)
        cmd = ['/usr/bin/osascript', '-l', 'JavaScript', '-e', script]
        output = subprocess.check_output(cmd)
        data = json.loads(output)
        if not data:
            log.debug('no URL for "%s"', name)
            continue
        title = data['title'] or 'Untitled'
        url = data['url']
        items.append(dict(title=title, subtitle=url, autocomplete=url,
                          icon=app, icontype='fileicon'))

    if not items:
        wf.add_item('No Clipboard or Browser URL',
                    'Enter a URL or copy one to clipboard.',
                    icon=ICON_WARNING)

    for item in items:
        wf.add_item(**item)

    wf.send_feedback()
Пример #13
0
def run(wf, argv):
    """Run ``searchio add`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    d = parse_args(wf, args)

    s = engines.Search.from_dict(d)

    if not util.valid_url(d['search_url']):
        raise ValueError('Invalid search URL: {!r}'.format(d['search_url']))

    if d['suggest_url'] and not util.valid_url(d['suggest_url']):
        raise ValueError('Invalid suggest URL: {!r}'.format(d['suggest_url']))

    p = ctx.search(s.uid)

    with open(p, 'wb') as fp:
        json.dump(s.dict, fp, sort_keys=True, indent=2)
    # m.save(**d)

    log.debug('Adding new search to info.plist ...')

    notify('Added New Search', d['title'])
Пример #14
0
def run(wf, argv):
    """Run ``searchio add`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    d = parse_args(wf, args)

    s = engines.Search.from_dict(d)

    if not util.valid_url(d['search_url']):
        raise ValueError('Invalid search URL: {!r}'.format(d['search_url']))

    if d['suggest_url'] and not util.valid_url(d['suggest_url']):
        raise ValueError('Invalid suggest URL: {!r}'.format(d['suggest_url']))

    p = ctx.search(s.uid)

    with open(p, 'wb') as fp:
        json.dump(s.dict, fp, sort_keys=True, indent=2)
    # m.save(**d)

    log.debug('Adding new search to info.plist ...')

    notify('Added New Search', d['title'])
Пример #15
0
def add_script_filters(wf, data, searches=None):
    """Add user searches to info.plist data."""
    ctx = Context(wf)
    only = set()

    if searches:  # add them to the user's searches dir
        for s in searches:
            path = os.path.join(ctx.searches_dir, s.uid + '.json')
            with open(path, 'wb') as fp:
                json.dump(s.dict, fp)
            only.add(s.uid)
            log.info('Saved search "%s"', s.title)

    f = util.FileFinder([ctx.searches_dir], ['json'])
    searches = [Search.from_file(p) for p in f]
    if only:
        searches = [s for s in searches if s.uid in only]

    searches.sort(key=lambda s: s.title)

    ypos = YPOS
    for s in searches:
        if not s.keyword:
            log.error('No keyword for search "%s" (%s)', s.title, s.uid)
            continue

        d = readPlistFromString(SCRIPT_FILTER)
        d['uid'] = s.uid
        d['config']['title'] = s.title
        # d['config']['script'] = './searchio search {} "$1"'.format(s.uid)
        d['config']['script'] = './search {} "$1"'.format(s.uid)
        d['config']['keyword'] = s.keyword
        data['objects'].append(d)
        data['connections'][s.uid] = [{
            'destinationuid': OPEN_URL_UID,
            'modifiers': 0,
            'modifiersubtext': '',
            'vitoclose': False,
        }]
        data['uidata'][s.uid] = {
            'note': s.title,
            'xpos': XPOS,
            'ypos': ypos,
        }
        ypos += YOFFSET
        log.info('Added Script Filter "%s" (%s)', s.title, s.uid)

    link_icons(wf, searches)
Пример #16
0
def run(wf, argv):
    """Run ``searchio variants`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    engine_id = wf.decode(args.get('<engine>') or '').strip()
    query = wf.decode(args.get('<query>') or '').strip()
    ICON_BACK = ctx.icon('back')

    engs = engines.load(*ctx.engine_dirs)
    for e in engs:
        if e.uid == engine_id:
            engine = e
            break
    else:
        raise ValueError('Unknown engine : {!r}'.format(engine_id))

    # get user searches so we can highlight already-installed searches
    uids = set([
        util.path2uid(p) for p in util.FileFinder([ctx.searches_dir], ['json'])
    ])

    log.debug('engine=%r', engine)
    variants = engine.variants

    def _key(s):
        return u'{} {}'.format(s.uid, s.title.lower())

    if query:
        variants = wf.filter(query, variants, _key)
    else:
        it = wf.add_item(u'All Engines \U00002026',
                         u'Go back to engine list',
                         arg=engine.uid,
                         valid=True,
                         icon=ICON_BACK)
        it.setvar('action', 'back')

        variants.sort()

    # ---------------------------------------------------------
    # Show results

    if args.get('--text') or util.textmode():  # Display for terminal

        print(_text_help.format(engine=engine), file=sys.stderr)

        table = util.Table([u'ID', u'Installed', u'Title'])
        for v in variants:
            installed = (u'', u'yes')[v.uid in uids]
            table.add_row((v.uid, installed, v.title))

        print(table)
        print()

    else:  # Display for Alfred
        wf.setvar('action', 'new')
        # TODO: Delimited browse action instead of nested Script Filters?
        icon = ctx.icon(engine.uid)

        for v in variants:
            it = wf.add_item(v.title,
                             u'{} > {}'.format(engine.title, v.name),
                             arg=v.uid,
                             valid=True,
                             icon=icon)
            it.setvar('engine', engine.uid)
            it.setvar('uid', v.uid)
            it.setvar('title', v.title)
            it.setvar('name', v.name)
            it.setvar('icon', icon)
            it.setvar('jsonpath', v.jsonpath)
            it.setvar('search_url', v.search_url)
            it.setvar('suggest_url', v.suggest_url)
            if v.pcencode:
                it.setvar('pcencode', '1')

        wf.send_feedback()

    return
Пример #17
0
def run(wf, argv):
    """Run ``searchio variants`` sub-command."""
    args = docopt(usage(wf), argv)
    ctx = Context(wf)
    engine_id = wf.decode(args.get('<engine>') or '').strip()
    query = wf.decode(args.get('<query>') or '').strip()
    ICON_BACK = ctx.icon('back')

    engs = engines.load(*ctx.engine_dirs)
    for e in engs:
        if e.uid == engine_id:
            engine = e
            break
    else:
        raise ValueError('Unknown engine : {!r}'.format(engine_id))

    # get user searches so we can highlight already-installed searches
    uids = set([util.path2uid(p) for p in
                util.FileFinder([ctx.searches_dir], ['json'])])

    log.debug('engine=%r', engine)
    variants = engine.variants

    def _key(s):
        return u'{} {}'.format(s.uid, s.title.lower())

    if query:
        variants = wf.filter(query, variants, _key)
    else:
        it = wf.add_item(
            u'All Engines \U00002026',
            u'Go back to engine list',
            arg=engine.uid,
            valid=True,
            icon=ICON_BACK)
        it.setvar('action', 'back')

        variants.sort()

    # ---------------------------------------------------------
    # Show results

    if args.get('--text') or util.textmode():  # Display for terminal

        print(_text_help.format(engine=engine), file=sys.stderr)

        table = util.Table([u'ID', u'Installed', u'Title'])
        for v in variants:
            installed = (u'', u'yes')[v.uid in uids]
            table.add_row((v.uid, installed, v.title))

        print(table)
        print()

    else:  # Display for Alfred
        wf.setvar('action', 'new')
        # TODO: Delimited browse action instead of nested Script Filters?
        icon = ctx.icon(engine.uid)

        for v in variants:
            it = wf.add_item(
                v.title,
                u'{} > {}'.format(engine.title, v.name),
                arg=v.uid,
                valid=True,
                icon=icon)
            it.setvar('engine', engine.uid)
            it.setvar('uid', v.uid)
            it.setvar('title', v.title)
            it.setvar('name', v.name)
            it.setvar('icon', icon)
            it.setvar('jsonpath', v.jsonpath)
            it.setvar('search_url', v.search_url)
            it.setvar('suggest_url', v.suggest_url)
            if v.pcencode:
                it.setvar('pcencode', '1')

        wf.send_feedback()

    return
Пример #18
0
def run(wf, argv):
    """Run ``searchio list`` sub-command."""
    ctx = Context(wf)
    ICON_ACTIVE = ctx.icon('searches-active')
    ICON_UPDATE_AVAILABLE = ctx.icon('update-available')
    ICON_UPDATE_NONE = ctx.icon('update-check')
    ICON_HELP = ctx.icon('help')
    ICON_IMPORT = ctx.icon('import')
    ICON_RELOAD = ctx.icon('reload')
    ICON_ON = ctx.icon('toggle-on')
    ICON_OFF = ctx.icon('toggle-off')

    args = docopt(usage(wf), argv)

    log.debug('args=%r', args)
    query = wf.decode(args.get('<query>') or '').strip()

    # ---------------------------------------------------------
    # Configuration items

    items = []

    if wf.update_available:
        items.append(dict(
            title=u'Update Available \U00002026',
            subtitle=u'Action to install now',
            autocomplete=u'workflow:update',
            valid=False,
            icon=ICON_UPDATE_AVAILABLE,
        ))

    items.append(dict(
        title=u'Installed Searches \U00002026',
        subtitle=u'Your configured searches',
        arg=u'user',
        valid=True,
        icon=ICON_ACTIVE,
    ))

    items.append(dict(
        title=u'All Engines \U00002026',
        subtitle=u'View supported engines and add new searches',
        arg=u'engines',
        valid=True,
        icon=u'icon.png',
    ))

    items.append(dict(
        title=u'Import Search \U00002026',
        subtitle=u'Add a search from a URL',
        arg=u'import',
        valid=True,
        icon=ICON_IMPORT,
    ))

    items.append(dict(
        title=u'Reload',
        subtitle=u'Re-create your searches',
        arg=u'reload',
        valid=True,
        # autocomplete=u'workflow:help',
        # valid=False,
        icon=ICON_RELOAD,
    ))

    icon = ICON_ON if ctx.getbool('SHOW_QUERY_IN_RESULTS') else ICON_OFF
    items.append(dict(
        title=u'Show Query in Results',
        subtitle=u'Always add query to end of results',
        arg=u'toggle-show-query',
        valid=True,
        # autocomplete=u'workflow:help',
        # valid=False,
        icon=icon,
    ))

    icon = ICON_ON if ctx.getbool('ALFRED_SORTS_RESULTS') else ICON_OFF
    items.append(dict(
        title=u'Alfred Sorts Results',
        subtitle=u"Apply Alfred's knowledge to suggestions",
        arg=u'toggle-alfred-sorts',
        valid=True,
        # autocomplete=u'workflow:help',
        # valid=False,
        icon=icon,
    ))

    items.append(dict(
        title=u'Online Help',
        subtitle=u'Open the help page in your browser',
        arg=u'help',
        valid=True,
        # autocomplete=u'workflow:help',
        # valid=False,
        icon=ICON_HELP,
    ))

    if not wf.update_available:
        items.append(dict(
            title=u'Workflow up to Date',
            subtitle=u'Action to check for a newer version now',
            autocomplete=u'workflow:update',
            valid=False,
            icon=ICON_UPDATE_NONE,
        ))

    # ---------------------------------------------------------
    # Show results

    if query:
        items = wf.filter(query, items, key=itemgetter('title'))

    if not items:
        wf.add_item('No matching items',
                    'Try a different query?',
                    icon=ICON_WARNING)

    for d in items:
        wf.add_item(**d)

    wf.send_feedback()
    return