def exists(*args, **kwargs):
    func, kwargs = _func(kwargs, func_local=os.path.exists,
                         func_remote=fabric.contrib.files.exists)
    if func == os.path.exists:
        args_list = list(args)
        args_list[0] = os.path.expanduser(args[0])
        args = tuple(args_list)
    return func(*args, **kwargs) def run(*args, **kwargs):
    func, kwargs = _func(kwargs)
    return func(*args, **kwargs) def task(func, *args, **kwargs):
    '''Composition of decorator functions for inherent self-documentation on
    task execution.

    On execution, each task prints out its name and its first docstring line.
    '''
    prefix = '\n# '
    tail = '\n'
    return fabric.api.task(
        print_full_name(color=magenta,
                        prefix=prefix,
                        tail=tail)(print_doc1(func)),
        *args,
        **kwargs) def print_msg(msg):
    if msg is not None:
        print(cyan(flo('{msg}'))) def suggest_localhost(func):
    '''Decorator: prompt user for value of env.host_string with default to
    'localhost' when env.host_string is empty.

    Modification of decorator function fabric.network.needs_host
    '''
    from fabric.network import handle_prompt_abort, to_dict

    @wraps(func)
    def host_prompting_wrapper(*args, **kwargs):
        while not env.get('host_string', False):
            handle_prompt_abort("the target host connection string")
            host_string = raw_input("No hosts found. Please specify "
                                    "host string for connection [localhost]: ")
            if host_string == '':
                host_string = 'localhost'
            env.update(to_dict(host_string))
        return func(*args, **kwargs)

    host_prompting_wrapper.undecorated = func

    return host_prompting_wrapper def checkup_git_repo_legacy(url, name=None, base_dir='~/repos',
                            verbose=False, prefix='', postfix=''):
    '''Checkout or update a git repo.'''
    if not name:
        match = re.match(r'.*/(.+)\.git', url)
        assert match, flo("Unable to extract repo name from '{url}'")
        name = match.group(1)
    assert name is not None, flo('Cannot extract repo name from repo: {url}')
    assert name != '', flo('Cannot extract repo name from repo: {url} (empty)')
    if verbose:
        name_blue = blue(name)
        print_msg(flo('{prefix}Checkout or update {name_blue}{postfix}'))
    if not exists(base_dir):
        run(flo('mkdir -p {base_dir}'))
    if not exists(flo('{base_dir}/{name}/.git')):
        run(flo('  &&  '.join([
                'cd {base_dir}',
                'git clone  {url}  {name}'])),
            msg='clone repo')
    else:
        if verbose:
            print_msg('update: pull from origin')
        run(flo('cd {base_dir}/{name}  &&  git pull'))
    return name def subtask(*args, **kwargs):
    '''Decorator which prints out the name of the decorated function on
    execution.
    '''
    depth = kwargs.get('depth', 2)
    prefix = kwargs.get('prefix', '\n' + '#' * depth + ' ')
    tail = kwargs.get('tail', '\n')
    doc1 = kwargs.get('doc1', False)
    color = kwargs.get('color', cyan)

    def real_decorator(func):
        if doc1:
            return print_full_name(color=color, prefix=prefix,
                                   tail=tail)(print_doc1(func))
        return print_full_name(color=color, prefix=prefix, tail=tail)(func)

    invoked = bool(not args or kwargs)
    if not invoked:
        # invoke decorator function which returns the wrapper function
        return real_decorator(func=args[0])
    return real_decorator def subsubtask(*args, **kwargs):

    def real_decorator(func):
        return subtask(depth=3, color=default_color, *args, **kwargs)(func)

    invoked = bool(not args or kwargs)
    if not invoked:
        # invoke decorator function which returns the wrapper function
        return real_decorator(func=args[0])
    return real_decorator def install_file_legacy(path, sudo=False, from_path=None, **substitutions):
    '''Install file with path on the host target.

    The from file is the first of this list which exists:
     * custom file
     * custom file.template
     * common file
     * common file.template
    '''
    # source paths 'from_custom' and 'from_common'
    from_path = from_path or path
    # remove beginning '/' (if any), eg '/foo/bar' -> 'foo/bar'
    from_tail = join('files', from_path.lstrip(os.sep))
    if from_path.startswith('~/'):
        from_tail = join('files', 'home', 'USERNAME',
                         from_path[2:])  # without beginning '~/'
    from_common = join(FABFILE_DATA_DIR, from_tail)
    from_custom = join(FABSETUP_CUSTOM_DIR, from_tail)

    # target path 'to_' (path or tempfile)
    for subst in ['SITENAME', 'USER', 'ADDON', 'TASK']:
        sitename = substitutions.get(subst, False)
        if sitename:
            path = path.replace(subst, sitename)
    to_ = path
    if sudo:
        to_ = join(os.sep, 'tmp', 'fabsetup_' + os.path.basename(path))
    path_dir = dirname(path)

    # copy file
    if isfile(from_custom):
        run(flo('mkdir -p  {path_dir}'))
        put(from_custom, to_)
    elif isfile(from_custom + '.template'):
        _install_file_from_template_legacy(from_custom + '.template', to_=to_,
                                    **substitutions)
    elif isfile(from_common):
        run(flo('mkdir -p  {path_dir}'))
        put(from_common, to_)
    else:
        _install_file_from_template_legacy(from_common + '.template', to_=to_,
                                    **substitutions)
    if sudo:
        run(flo('sudo mv --force  {to_}  {path}')) @needs_host
def update_or_append_line(filename, prefix, new_line, keep_backup=True,
                          append=True):
    '''Search in file 'filename' for a line starting with 'prefix' and replace
    the line by 'new_line'.

    If a line starting with 'prefix' not exists 'new_line' will be appended.
    If the file not exists, it will be created.

    Return False if new_line was appended, else True (i.e. if the prefix was
    found within of the file).
    '''
    result = None
    if env.host_string == 'localhost':
        result = update_or_append_local(filename, prefix, new_line,
                                        keep_backup, append)
    else:
        tmp_dir = tempfile.mkdtemp(suffix='', prefix='fabsetup_')
        # fabric.api.local(flo('chmod 777 {tmp_dir}'))
        local_path = os.path.join(tmp_dir, os.path.basename(filename))
        fabric.operations.get(remote_path=filename, local_path=local_path,
                              use_sudo=True, temp_dir='/tmp')
        result = update_or_append_local(local_path, prefix, new_line,
                                        keep_backup, append)
        put(local_path, remote_path=filename, use_sudo=True, temp_dir='/tmp')
        with quiet():
            fabric.api.local(flo('rm -rf {tmp_dir}'))
    return result def needs_repo_fabsetup_custom(func):
    '''Decorator, ensures that fabsetup-custom exists and it is a git repo.'''
    from fabric.api import local

    @wraps(func)
    def wrapper(*args, **kwargs):
        if not os.path.exists(FABSETUP_CUSTOM_DIR):
            msg = '''\
Git repository ~/.fabsetup-custom with configurations does not exist.
This configs are required to use fabsetup.

Clone it if you already have your own fabsetup-custom repository:

    git clone <user>@<hostname>:/path/to/fabsetup-custom.git  ~/.fabetup-custom

Else, initialize a new repository.

Init a new repository `~/.fabsetup-custom`?'''
            if not query_yes_no(msg, default='yes'):
                sys.exit('abort')
            custom_dir = FABSETUP_CUSTOM_DIR
            presetting_dir = join(FABFILE_DATA_DIR,
                                  'presetting-fabsetup-custom')
            if not isdir(custom_dir):
                print(yellow('\n** **     Init ') +
                      yellow('~/.fabsetup-custom', bold=True) +
                      yellow('      ** **\n'))
                print(yellow(flo('** Create files in dir {custom_dir} **')))
                local(flo('mkdir -p {custom_dir}'))
                local(flo('cp -r --no-clobber {presetting_dir}/. {custom_dir}'))
                import_fabsetup_custom(globals())
            else:
                with quiet():
                    local(flo(
                        'cp -r --no-clobber {presetting_dir}/. {custom_dir}'))

            if not isdir(join(custom_dir, '.git')):
                print(yellow(
                    '\n** Git repo ~/.fabsetup-custom: '
                    'init and first commit **'))
                local(flo('cd {custom_dir} && git init'))
                local(flo('cd {custom_dir} && git add .'))
                local(flo('cd {custom_dir} && git commit -am "Initial commit"'))
                print(yellow("** Done. Don't forget to create a backup of your "
                             '~/.fabsetup-custom repo **\n'))
                print(yellow("** But do not make it public, it's custom **\n",
                             bold=True))
            else:
                with quiet():
                    cmd = flo('cd {custom_dir} && git status --porcelain')
                    res = local(cmd, capture=True)
                    if res:
                        print(yellow('\n** git repo  ') +
                              magenta('~/.fabsetup-custom  ') +
                              yellow('has uncommitted changes: **'))
                        print(cmd)
                        print(yellow(res, bold=True))
                        print(yellow(
                            "** Don't forget to commit them and make a "
                            "backup of your repo **\n"))
        return func(*args, **kwargs)
    return wrapper def flo(string):
    '''Return the string given by param formatted with the callers locals.'''
    callers_locals = {}
    frame = inspect.currentframe()
    try:
        outerframe = frame.f_back
        callers_locals = outerframe.f_locals
    finally:
        del frame
    return string.format(**callers_locals) def query_input(question, default=None, color=default_color):
    """Ask a question for input via raw_input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits <Enter>.

    The "answer" return value is a str.
    """
    if default is None or default == '':
        prompt = ' '
    elif type(default) == str:
        prompt = flo(' [{default}] ')
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(color(question + prompt))
        choice = raw_input()
        if default is not None and choice == '':
            return default
        if choice != '':
            return choice def query_yes_no(question, default="yes"):
    """Ask a yes/no question via raw_input() and return their answer.

    "question" is a string that is presented to the user.
    "default" is the presumed answer if the user just hits <Enter>.
    It must be "yes" (the default), "no", or None (which means an answer
    of the user is required).

    The "answer" return value is True for "yes" or False for "no".
    """
    valid = {"yes": True, "y": True, "ye": True, '1': True,
             "no": False, "n": False, '0': False, }
    if default is None:
        prompt = " [y/n] "
    elif default == "yes":
        prompt = " [Y/n] "
    elif default == "no":
        prompt = " [y/N] "
    else:
        raise ValueError("invalid default answer: '%s'" % default)

    while True:
        sys.stdout.write(question + prompt)
        choice = raw_input().lower()
        if default is not None and choice == '':
            return valid[default]
        elif choice in valid:
            return valid[choice]
        else:
            sys.stdout.write("Please respond with 'yes' or 'no' "
                             "(or 'y' or 'n').\n")
import os
from fabric.context_managers import quiet
from fabric.api import execute
from fabsetup.fabutils import exists, run, task, print_msg, suggest_localhost
from fabsetup.fabutils import checkup_git_repo_legacy, subtask, subsubtask
from fabsetup.fabutils import install_file_legacy
from fabsetup.fabutils import update_or_append_line, needs_repo_fabsetup_custom
from fabsetup.utils import flo, query_input, query_yes_no
    from config import basedir, github_user, github_repo
        print_msg('git repo already initialized (skip)')
    else:
        if not exists('{basedir_abs}/.gitignore'):
            install_file_legacy(path=flo('{basedir_abs}/.gitignore'),
                         from_path='~/repos/my_presi/.gitignore')
        run(flo('cd {basedir} && git init'))
        run(flo('cd {basedir} && git add .'))
        run(flo('cd {basedir} && git commit -am "Initial commit"'))


def remote_origin_configured(basedir):
    entry_exists = False
    with quiet():
        entry_exists = run(flo('''grep '\[remote "origin"\]' '''
                           '{basedir}/.git/config')).return_code == 0
    return entry_exists


def _insert_repo_infos_into_readme(basedir, github_user, github_repo):
    print_msg('\nadd github user and repo name into README.md')
    filename = flo('{basedir}/README.md')
    # FIXME: workaround: line starts with space "trick" for github.io link
    #        correct solution would be: create util function update_line()
    #                                   which will not append if prefix not
    #                                   found in file
    update_or_append_line(filename,
                          #prefix=' https://USER.github.io/REPO',
                          prefix=' https://',
                          new_line=flo(' https://{github_user}.github.io/'
                                       '{github_repo}'))
    update_or_append_line(filename,
                          #prefix='https://github.com/USER/REPO/blob/master/'
                          #       'slides.md',
                          prefix='https://github.com/',
                          new_line=flo('https://github.com/{github_user}/'
                                       '{github_repo}/blob/master/slides.md'))


def _create_github_remote_repo(basedir):
    '''More info:
      https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
      https://developer.github.com/v3/repos/#create
      https://stackoverflow.com/a/10325316
    '''
    github_user = _lazy('github_user')
    github_repo = _lazy('github_repo', default=os.path.basename(basedir))

    _insert_repo_infos_into_readme(basedir, github_user, github_repo)
    run(flo('cd {basedir} && git add README.md'))
    run(flo('cd {basedir} && git commit -am "Add github repo and user '
            'into README.md"'))

    print_msg('create github repo')
    run(flo("cd {basedir}  &&  "
            "curl -u '{github_user}' https://api.github.com/user/repos "
            "-d '") + '{"name":"' + flo('{github_repo}"') + "}'")
    run(flo('cd {basedir}  &&  '
            'git remote add origin '
            'git@github.com:{github_user}/{github_repo}.git'))
    run(flo('cd {basedir}  &&  git push origin master'))


@subtask
def create_github_remote_repo(basedir):
    '''Create a remote origin repo at github.com if wanted.'''
    if remote_origin_configured(basedir):
        print_msg('remote origin already configured (skip)')
    elif query_yes_no('Create remote repo at github.com?', default='no'):
        _create_github_remote_repo(basedir)


@subtask
def setup_npm(revealjs_dir):
    if query_yes_no('\nSet up npm for serving presentation?',
                    default='no'):
        run(flo('cd {revealjs_dir}  &&  '
                'npm install'))
        print_msg(flo('\nServe presentation locally '
                      '(monitors source files for changes):\n\n'
                      '    cd {revealjs_dir}  &&  npm start\n\n'
                      'View presentation in a browser:\n\n'
                      '    http://localhost:8000'))


@task
@suggest_localhost
def decktape():
    '''Install DeckTape.

    DeckTape is a "high-quality PDF exporter for HTML5 presentation
    frameworks".  It can be used to create PDFs from reveal.js presentations.

    More info:
      https://github.com/astefanutti/decktape
      https://github.com/hakimel/reveal.js/issues/1252#issuecomment-198270915
    '''
    run('mkdir -p ~/bin/decktape')
    if not exists('~/bin/decktape/decktape-1.0.0'):
        print_msg('\n## download decktape 1.0.0\n')
        run('cd ~/bin/decktape && '
            'curl -L https://github.com/astefanutti/decktape/archive/'
            'v1.0.0.tar.gz | tar -xz --exclude phantomjs')
        run('cd ~/bin/decktape/decktape-1.0.0 && '
            'curl -L https://github.com/astefanutti/decktape/releases/'
            'download/v1.0.0/phantomjs-linux-x86-64 -o phantomjs')
        run('cd ~/bin/decktape/decktape-1.0.0 && '
            'chmod +x phantomjs')
    run('ln -snf ~/bin/decktape/decktape-1.0.0 ~/bin/decktape/active',
        msg='\n## link installed decktape version as active')
    print_msg('\nCreate PDF from reveal.js presentation:\n\n    '
              '# serve presentation:\n    '
              'cd ~/repos/my_presi/reveal.js/ && npm start\n\n    '
              '# create pdf in another shell:\n    '
              'cd ~/bin/decktape/active && \\\n    '
              './phantomjs decktape.js --size 1280x800  localhost:8000  '
              '~/repos/my_presi/my_presi.pdf')


@task
@needs_repo_fabsetup_custom  # for import of github_user from config.py

@suggest_localhost