Module anchorage.cli

Anchorage CLI

Expand source code
#!/usr/run/env python37
# SPDX-FileCopyrightText: © 2021 Antonio López Rivera <antonlopezr99@gmail.com>
# SPDX-License-Identifier: GPL-3.0-only

"""
Anchorage CLI
-------------
"""

from __future__ import print_function, unicode_literals

import sys

from alexandria.shell import print_color

from anchorage.bookmarks import bookmarks, path, load
from anchorage.anchor import anchor_online, anchor_locally
from anchorage.anchor_infrs.infrastructure import init, check_install, read_config
from anchorage.anchor_utils.shell import shell
from anchorage.anchor_utils.system import operating_system
from anchorage.anchor_utils.aesthetic import newline, title


try:
    from PyInquirer import style_from_dict, Token, prompt
except ImportError:
    print("PyInquirer import error: prompt-toolkits version != 1.0.14")
    print("Installing prompt-toolkit==1.0.14")
    shell("pip install prompt-toolkit==1.0.14")
    sys.exit("Success! Please run again")


def main():
    """
    # Anchorage library CLI.

    ## 1. Dependency check
        - Choice: overwrite config.toml if found
    ## 2. Browser choice
    ## 3. Bookmark filter
        - Local files
        - String match
            - Directories
            - Bookmark name
            - Bookmark URL
        - Substring match
            - Directories
            - Bookmark name
            - Bookmark URL
        - Regex match
            - Directories
            - Bookmark name
            - Bookmark URL
        - *Duplicate URLs are excluded by default*
    ## 4. Archive choice
        - Online
            - Skip sites already saved in TIA, or generate current snapshots for all sites in the collection
        - Local
            - Local archive directory input
    ## 5. Confirmation
    """

    # Title
    print('\nWelcome to the')
    title("Anchorage  cli")
    print("                                        Antonio Lopez Rivera, 2021\n")

    # CLI interface look
    style = style_from_dict({
        Token.QuestionMark:     '#E91E63 bold',
        Token.Selected:         '#673AB7 bold',
        Token.Instruction:      '',                # default
        Token.Answer:           '#9cdeff bold',
        Token.Question:         '',
    })

    """
    Dialogue
    """
    # 1. Dependency check
    dp_check = [{
                    'type': 'confirm',
                    'name': 'ready',
                    'message': 'Are you ready to proceed?',
                    'default': True
                }]

    if not prompt(dp_check, style=style)['ready']:
        print_color("\n     ~Operation cancelled\n", "red")
        sys.exit()

    print_color("\n     ~Running dependency checks", "white")
    shell("pip3 install --upgrade anchorage")

    if check_install() == 0:
        print("")
        reset_config = [{
            'type': 'confirm',
            'name': 'reset',
            'message': f'Previous config detected: reset config.toml?',
            'default': False,
        }]
        reset = prompt(reset_config, style=style)['reset']
        newline()
    else:
        reset = False

    init(reset)

    print_color("     ~Everything in order!", "green")
    print_color("     ~Edit configuration in ~/.anchorage/config.toml\n", "green")

    # 2. Browser choice
    cfg = read_config()
    brow_choice = [{
                    'type': 'list',
                    'name': 'browser',
                    'message': f'Please choose the browser from which to archive all bookmarks.',
                    'choices': list(map(lambda s: s.title(), list(cfg.keys()))),
                    'filter': lambda n: n.lower()
                   }]
    browser = prompt(brow_choice, style=style)['browser']
    newline()
    if cfg[browser][operating_system()] == "?":
        print_color("     ~The path to this browser hasn't been set up yet in your configuration file.", "red")
        print(f"     ~Edit ~/anchorage/config.toml to include the path for your {browser.title()} "
              f"bookmarks file and try again.\n")
        sys.exit()
    try:
        bmk_dict = load(path(browser))
    except FileNotFoundError:
        print_color("     ~Error: file not found.", "red")
        print_color("\n     ~Edit ~/anchorage/config.toml to include the correct bookmark file path "
                          "for your browser and try again.\n", "red")
        sys.exit()

    # 3. Bookmark filter
    #     - Local files
    #     - By string match
    #     - By substring match
    #     - By regex match
    bk_filt = [{
                    'type': 'checkbox',
                    'name': 'drop',
                    'message': f'If desired, specify which elements to exclude from the archiving process.',
                    'choices': [
                        {
                            'name': 'Local files'
                        },
                        {
                            'name': 'Match string'
                        },
                        {
                            'name': 'Match substring'
                        },
                        {
                            'name': 'Regex'
                        },
                    ],
                    'filter': lambda lst: [n.lower() if isinstance(n, str) else n for n in lst]
                  }]

    filter_kind = lambda kind: [{
                                    'type': 'checkbox',
                                    'name': 'kind',
                                    'message': f'Specify targets for {kind} filter.',
                                    'choices': [
                                        {
                                            'name': 'Directories'
                                        },
                                        {
                                            'name': 'Names'
                                        },
                                        {
                                            'name': 'URLs'
                                        },
                                    ],
                                    'filter': lambda lst: [n.lower() if isinstance(n, str) else n for n in lst]
                                }]

    filter_input = lambda terms: prompt([{
                                            'type': 'input',
                                            'name': 'input',
                                            'message': f'Enter a comma-separated list of {terms} to filter out.',
                                            'default': '',
                                            'filter': lambda string: string.split(",")
                                        }], style=style)['input']

    filter_regex = lambda terms: prompt([{
                                            'type': 'input',
                                            'name': 'input',
                                            'message': f'Enter regex formula to filter out {terms}.',
                                            'default': ''
                                         }], style=style)['input']

    drop_list = prompt(bk_filt, style=style)['drop']
    newline()

    drop_dirs       = None
    drop_names      = None
    drop_urls       = None
    drop_dir_subs   = None
    drop_name_subs  = None
    drop_url_subs   = None
    drop_dir_regex  = None
    drop_name_regex = None
    drop_url_regex  = None

    if 'match string' in drop_list:
        kind = prompt(filter_kind('string'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dirs = filter_input('bookmark directory names')
            newline()
        if 'names' in kind:
            drop_names = filter_input('bookmark names')
            newline()
        if 'urls' in kind:
            drop_urls = filter_input('bookmark URLs')
            newline()
    if 'match substring' in drop_list:
        kind = prompt(filter_kind('substring'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dir_subs = filter_input('bookmark directory name substrings')
            newline()
        if 'names' in kind:
            drop_name_subs = filter_input('bookmark name substrings')
            newline()
        if 'urls' in kind:
            drop_url_subs = filter_input('bookmark URL substrings')
            newline()
    if 'regex' in drop_list:
        kind = prompt(filter_kind('regex'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dir_regex = filter_regex('bookmark directory names')
            newline()
        if 'names' in kind:
            drop_name_regex = filter_regex('bookmark names')
            newline()
        if 'urls' in kind:
            drop_url_regex = filter_regex('bookmark URLs')
            newline()

    print_color("     ~Applying filters to bookmark collection", "white")

    bmk = bookmarks(bmk_dict,
                    drop_local_files='local files' in drop_list,
                    drop_dirs=drop_dirs,
                    drop_names=drop_names,
                    drop_urls=drop_urls,
                    drop_dirs_subs=drop_dir_subs,
                    drop_names_subs=drop_name_subs,
                    drop_urls_subs=drop_url_subs,
                    drop_dirs_regex=drop_dir_regex,
                    drop_names_regex=drop_name_regex,
                    drop_urls_regex=drop_url_regex,
                    )
    n_bookmarks = len(bmk.bookmarks)
    n_directories = bmk.n_dirs
    print_color(f"     ~Done! Found {n_bookmarks} bookmarks in {n_directories} directories.\n", "green")

    # 4. Archive choice
    archive_choice = [{
                    'type': 'list',
                    'name': 'archive',
                    'message': f'Choose an archiving method.',
                    'choices': ['Online', 'Local'],
                    'filter': lambda n: n.split(' ')[0].lower()
                     }]

    archive = prompt(archive_choice, style=style)['archive']
    newline()

    if archive == 'online':         # Online archive: save existing entries?
        print_color(f"     ~By default websites will not be archived if a previous image "
                          f"exists in The Internet Archive.\n", "white")
        ovw_online = [{
                          'type': 'confirm',
                          'name': 'overwrite',
                          'message': 'Save snapshots even if previous ones exist?',
                          'default': False
                     }]
        overwrite = prompt(ovw_online, style=style)['overwrite']
        newline()
        if overwrite:
            print_color(f"     ~Current snapshots will be saved for your entire collection. "
                              f"Expect a runtime of {n_bookmarks*15/3600:.2f} to {n_bookmarks*60/3600:.2f} "
                              f"hours.\n", "green")
        else:
            print_color(f"     ~Expect a runtime of {n_bookmarks*1.5/3600:.2f} to "
                              f"{n_bookmarks*3/3600:.2f} hours.\n", "green")
    if archive == 'local':          # Local archive directory
        dir_prompt = [{
                          'type': 'input',
                          'name': 'dir',
                          'message': 'Enter the relative or full path of the archive directory.',
                          'default': './anchor'
                     }]
        archive_dir = prompt(dir_prompt, style=style)['dir']
        newline()

    # 5. Log level
    log_prompt = [{
                        'type': 'list',
                        'name': 'loglevel',
                        'message': 'Enter the relative or full path of the archive directory.',
                        'choices': ['  - Full log output', '  - Progress bar', '  - Suppress all output'],
                        'filter': lambda choice: {'Full log output'    : 0,
                                                  'Progress bar'       : 20,
                                                  'Suppress all output': 50}[choice[4:]]
                  }]
    loglevel = prompt(log_prompt, style=style)['loglevel']
    newline()

    # 6. Confirmation
    confirmation = lambda br, nb: [{
                                        'type': 'confirm',
                                        'name': 'go',
                                        'message': 'All set! Proceed to archive your'
                                                   + " " + str(br) + " " + str(nb) + " " +
                                                   'bookmark collection?',
                                        'default': True
                                   }]
    conf_prompt = confirmation(browser.title(), n_bookmarks)

    go = prompt(conf_prompt, style=style)['go']
    newline()

    if go:
        if archive == 'online':                     # Anchor online
            anchor_online(bmk,
                          overwrite=overwrite,
                          loglevel=loglevel)
        elif archive == 'local':                    # Anchor locally
            anchor_locally(bmk,
                           archive=archive_dir,
                           loglevel=loglevel)
    else:
        print_color("     ~Operation cancelled\n", "red")

Functions

def main()

Anchorage library CLI.

1. Dependency check

- Choice: overwrite config.toml if found

2. Browser choice

3. Bookmark filter

- Local files
- String match
    - Directories
    - Bookmark name
    - Bookmark URL
- Substring match
    - Directories
    - Bookmark name
    - Bookmark URL
- Regex match
    - Directories
    - Bookmark name
    - Bookmark URL
- *Duplicate URLs are excluded by default*

4. Archive choice

- Online
    - Skip sites already saved in TIA, or generate current snapshots for all sites in the collection
- Local
    - Local archive directory input

5. Confirmation

Expand source code
def main():
    """
    # Anchorage library CLI.

    ## 1. Dependency check
        - Choice: overwrite config.toml if found
    ## 2. Browser choice
    ## 3. Bookmark filter
        - Local files
        - String match
            - Directories
            - Bookmark name
            - Bookmark URL
        - Substring match
            - Directories
            - Bookmark name
            - Bookmark URL
        - Regex match
            - Directories
            - Bookmark name
            - Bookmark URL
        - *Duplicate URLs are excluded by default*
    ## 4. Archive choice
        - Online
            - Skip sites already saved in TIA, or generate current snapshots for all sites in the collection
        - Local
            - Local archive directory input
    ## 5. Confirmation
    """

    # Title
    print('\nWelcome to the')
    title("Anchorage  cli")
    print("                                        Antonio Lopez Rivera, 2021\n")

    # CLI interface look
    style = style_from_dict({
        Token.QuestionMark:     '#E91E63 bold',
        Token.Selected:         '#673AB7 bold',
        Token.Instruction:      '',                # default
        Token.Answer:           '#9cdeff bold',
        Token.Question:         '',
    })

    """
    Dialogue
    """
    # 1. Dependency check
    dp_check = [{
                    'type': 'confirm',
                    'name': 'ready',
                    'message': 'Are you ready to proceed?',
                    'default': True
                }]

    if not prompt(dp_check, style=style)['ready']:
        print_color("\n     ~Operation cancelled\n", "red")
        sys.exit()

    print_color("\n     ~Running dependency checks", "white")
    shell("pip3 install --upgrade anchorage")

    if check_install() == 0:
        print("")
        reset_config = [{
            'type': 'confirm',
            'name': 'reset',
            'message': f'Previous config detected: reset config.toml?',
            'default': False,
        }]
        reset = prompt(reset_config, style=style)['reset']
        newline()
    else:
        reset = False

    init(reset)

    print_color("     ~Everything in order!", "green")
    print_color("     ~Edit configuration in ~/.anchorage/config.toml\n", "green")

    # 2. Browser choice
    cfg = read_config()
    brow_choice = [{
                    'type': 'list',
                    'name': 'browser',
                    'message': f'Please choose the browser from which to archive all bookmarks.',
                    'choices': list(map(lambda s: s.title(), list(cfg.keys()))),
                    'filter': lambda n: n.lower()
                   }]
    browser = prompt(brow_choice, style=style)['browser']
    newline()
    if cfg[browser][operating_system()] == "?":
        print_color("     ~The path to this browser hasn't been set up yet in your configuration file.", "red")
        print(f"     ~Edit ~/anchorage/config.toml to include the path for your {browser.title()} "
              f"bookmarks file and try again.\n")
        sys.exit()
    try:
        bmk_dict = load(path(browser))
    except FileNotFoundError:
        print_color("     ~Error: file not found.", "red")
        print_color("\n     ~Edit ~/anchorage/config.toml to include the correct bookmark file path "
                          "for your browser and try again.\n", "red")
        sys.exit()

    # 3. Bookmark filter
    #     - Local files
    #     - By string match
    #     - By substring match
    #     - By regex match
    bk_filt = [{
                    'type': 'checkbox',
                    'name': 'drop',
                    'message': f'If desired, specify which elements to exclude from the archiving process.',
                    'choices': [
                        {
                            'name': 'Local files'
                        },
                        {
                            'name': 'Match string'
                        },
                        {
                            'name': 'Match substring'
                        },
                        {
                            'name': 'Regex'
                        },
                    ],
                    'filter': lambda lst: [n.lower() if isinstance(n, str) else n for n in lst]
                  }]

    filter_kind = lambda kind: [{
                                    'type': 'checkbox',
                                    'name': 'kind',
                                    'message': f'Specify targets for {kind} filter.',
                                    'choices': [
                                        {
                                            'name': 'Directories'
                                        },
                                        {
                                            'name': 'Names'
                                        },
                                        {
                                            'name': 'URLs'
                                        },
                                    ],
                                    'filter': lambda lst: [n.lower() if isinstance(n, str) else n for n in lst]
                                }]

    filter_input = lambda terms: prompt([{
                                            'type': 'input',
                                            'name': 'input',
                                            'message': f'Enter a comma-separated list of {terms} to filter out.',
                                            'default': '',
                                            'filter': lambda string: string.split(",")
                                        }], style=style)['input']

    filter_regex = lambda terms: prompt([{
                                            'type': 'input',
                                            'name': 'input',
                                            'message': f'Enter regex formula to filter out {terms}.',
                                            'default': ''
                                         }], style=style)['input']

    drop_list = prompt(bk_filt, style=style)['drop']
    newline()

    drop_dirs       = None
    drop_names      = None
    drop_urls       = None
    drop_dir_subs   = None
    drop_name_subs  = None
    drop_url_subs   = None
    drop_dir_regex  = None
    drop_name_regex = None
    drop_url_regex  = None

    if 'match string' in drop_list:
        kind = prompt(filter_kind('string'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dirs = filter_input('bookmark directory names')
            newline()
        if 'names' in kind:
            drop_names = filter_input('bookmark names')
            newline()
        if 'urls' in kind:
            drop_urls = filter_input('bookmark URLs')
            newline()
    if 'match substring' in drop_list:
        kind = prompt(filter_kind('substring'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dir_subs = filter_input('bookmark directory name substrings')
            newline()
        if 'names' in kind:
            drop_name_subs = filter_input('bookmark name substrings')
            newline()
        if 'urls' in kind:
            drop_url_subs = filter_input('bookmark URL substrings')
            newline()
    if 'regex' in drop_list:
        kind = prompt(filter_kind('regex'), style=style)['kind']
        newline()
        if 'directories' in kind:
            drop_dir_regex = filter_regex('bookmark directory names')
            newline()
        if 'names' in kind:
            drop_name_regex = filter_regex('bookmark names')
            newline()
        if 'urls' in kind:
            drop_url_regex = filter_regex('bookmark URLs')
            newline()

    print_color("     ~Applying filters to bookmark collection", "white")

    bmk = bookmarks(bmk_dict,
                    drop_local_files='local files' in drop_list,
                    drop_dirs=drop_dirs,
                    drop_names=drop_names,
                    drop_urls=drop_urls,
                    drop_dirs_subs=drop_dir_subs,
                    drop_names_subs=drop_name_subs,
                    drop_urls_subs=drop_url_subs,
                    drop_dirs_regex=drop_dir_regex,
                    drop_names_regex=drop_name_regex,
                    drop_urls_regex=drop_url_regex,
                    )
    n_bookmarks = len(bmk.bookmarks)
    n_directories = bmk.n_dirs
    print_color(f"     ~Done! Found {n_bookmarks} bookmarks in {n_directories} directories.\n", "green")

    # 4. Archive choice
    archive_choice = [{
                    'type': 'list',
                    'name': 'archive',
                    'message': f'Choose an archiving method.',
                    'choices': ['Online', 'Local'],
                    'filter': lambda n: n.split(' ')[0].lower()
                     }]

    archive = prompt(archive_choice, style=style)['archive']
    newline()

    if archive == 'online':         # Online archive: save existing entries?
        print_color(f"     ~By default websites will not be archived if a previous image "
                          f"exists in The Internet Archive.\n", "white")
        ovw_online = [{
                          'type': 'confirm',
                          'name': 'overwrite',
                          'message': 'Save snapshots even if previous ones exist?',
                          'default': False
                     }]
        overwrite = prompt(ovw_online, style=style)['overwrite']
        newline()
        if overwrite:
            print_color(f"     ~Current snapshots will be saved for your entire collection. "
                              f"Expect a runtime of {n_bookmarks*15/3600:.2f} to {n_bookmarks*60/3600:.2f} "
                              f"hours.\n", "green")
        else:
            print_color(f"     ~Expect a runtime of {n_bookmarks*1.5/3600:.2f} to "
                              f"{n_bookmarks*3/3600:.2f} hours.\n", "green")
    if archive == 'local':          # Local archive directory
        dir_prompt = [{
                          'type': 'input',
                          'name': 'dir',
                          'message': 'Enter the relative or full path of the archive directory.',
                          'default': './anchor'
                     }]
        archive_dir = prompt(dir_prompt, style=style)['dir']
        newline()

    # 5. Log level
    log_prompt = [{
                        'type': 'list',
                        'name': 'loglevel',
                        'message': 'Enter the relative or full path of the archive directory.',
                        'choices': ['  - Full log output', '  - Progress bar', '  - Suppress all output'],
                        'filter': lambda choice: {'Full log output'    : 0,
                                                  'Progress bar'       : 20,
                                                  'Suppress all output': 50}[choice[4:]]
                  }]
    loglevel = prompt(log_prompt, style=style)['loglevel']
    newline()

    # 6. Confirmation
    confirmation = lambda br, nb: [{
                                        'type': 'confirm',
                                        'name': 'go',
                                        'message': 'All set! Proceed to archive your'
                                                   + " " + str(br) + " " + str(nb) + " " +
                                                   'bookmark collection?',
                                        'default': True
                                   }]
    conf_prompt = confirmation(browser.title(), n_bookmarks)

    go = prompt(conf_prompt, style=style)['go']
    newline()

    if go:
        if archive == 'online':                     # Anchor online
            anchor_online(bmk,
                          overwrite=overwrite,
                          loglevel=loglevel)
        elif archive == 'local':                    # Anchor locally
            anchor_locally(bmk,
                           archive=archive_dir,
                           loglevel=loglevel)
    else:
        print_color("     ~Operation cancelled\n", "red")