text: import os
  import getpass
  import sys
  import configparser
  import re
  import glob
  import shutil
  
  import click
  import pyperclip
  import webbrowser
  from prompt_toolkit import prompt
  
  from .api import SnippetApi
  from .snippet import Snippet
  from .completers import SnippetFileCompleter, SnippetDirCompleter, ValidateSnippetDir, ValidateSnippetFile
  from .repo import Repo
  from . import utils
  
  DEFAULT_SNIPPET_DIR = os.path.expanduser('~/.snippets')
  DEFAULT_SNIPPER_CONFIG = os.path.expanduser('~/.snipperrc')
  
  
  @click.group(context_settings={'help_option_names': ['-h', '--help']})
  @click.option(
      '--config', '-C', 'config_file',
      default=DEFAULT_SNIPPER_CONFIG,
      type=click.Path(),
      help='Config file path: Default: {}'.format(DEFAULT_SNIPPER_CONFIG)
  )
  @click.option(
      '--no-color',
      default=False,
      is_flag=True,
      help='Don\'t colorize output',
  )
  @click.pass_context
  def cli(ctx, config_file, no_color, **kwargs):
  
      if not os.path.exists(config_file):
          print('Configuration file not found. Plase give me your settings.')
          _init_snipper(config_file, not no_color)
  
      # Create config with default values
      config = configparser.ConfigParser({
          'snippet_dir': DEFAULT_SNIPPET_DIR,
          'auto_push': 'yes',
          'default_filename': 'file.txt',
          'colorize': 'no' if no_color else 'yes',
      })
  
      # Overwrite config with user config.
      config.read(config_file)
  
      # https protocol not supported because
      # user must give password every clone/pull/push.
      config.set('snipper', 'protocol', 'ssh')
  
      # Read useraname/password from env vars not exist in config file
      if not config.has_option('snipper', 'username') and os.environ.get('SNIPPER_USERNAME'):
          config.set('snipper', 'username', os.environ['SNIPPER_USERNAME'])
  
      if not config.has_option('snipper', 'password') and os.environ.get('SNIPPER_PASSWORD'):
          config.set('snipper', 'password', os.environ['SNIPPER_PASSWORD'])
  
      ctx.obj = config
  
      # Sync snippets if not synced before
      if not os.path.exists(utils.get_owner_metadata_path(config)):
          if not os.path.exists(config.get('snipper', 'snippet_dir')):
              os.makedirs(config.get('snipper', 'snippet_dir'))
  
          utils.secho(not no_color, "Syncing snippets...", fg='green')
          _sync_snippets(config)
  
  
  def _init_snipper(config_file, colorize):
  
      if os.path.exists(config_file) and not click.confirm('Config file already exist. Overwrite it'):
          return
  
      snippet_dir = click.prompt('Where to keep snippets', default=DEFAULT_SNIPPET_DIR)
      username = click.prompt('Bitbucket username')
      password_help_text = '\n'.join([
          "You should give me a password for authenticating to Bitbucket API for accessing your snippets.",
          "You can create an app password that only permitted",
          "to snippets at settings page on bitbucket.org",
      ])
  
      utils.secho(colorize, password_help_text, fg='blue')
  
      password = getpass.getpass('App password:')
  
      # Create snippet home dir
      if not os.path.exists(snippet_dir):
          os.makedirs(snippet_dir)
  
      config = configparser.ConfigParser()
      config.read(config_file)
  
      config.add_section('snipper')
  
      config.set('snipper', 'snippet_dir', snippet_dir)
      config.set('snipper', 'username', username)
      config.set('snipper', 'password', password)
      config.set('snipper', 'colorize', 'yes' if colorize else 'no')
  
      config.write(open(config_file, 'w'))
  
  
  def _sync_snippets(config, **kwargs):
      colorize = config.getboolean('snipper', 'colorize')
      snippet_id = kwargs.get('snippet_id')
  
      api = SnippetApi(config)
  
      utils.secho(colorize, ':: Waiting for download changes from Bitbucket...', fg='blue')
  
      data = api.get_all()
  
      utils.update_metadata(config, data)
  
      pulling_process_count = 0
      cloning_process_count = 0
      snippet = None
  
      for item in data['values']:
  
          if snippet_id and item['id'] != snippet_id:
              continue
  
          # Show files in snippet
          snippet = Snippet(config, item)
  
          if not snippet.is_exists():
              # If snippet not exist in local, clone snippet
              cloning_process_count += 1
              snippet.clone()
          else:
              # Commit changes if exist before pull new changes from remote.
              snippet.commit()
              snippet.sync()
              snippet.update_dir_name()
              pulling_process_count += 1
  
      if snippet_id and not snippet:
          utils.secho(colorize, 'Snippet with given id not found: {}'.format(snippet_id), fg='yellow')
  
      if pulling_process_count:
          utils.secho(colorize, ':: Waiting for {} processes to finish sync snippets...'.format(pulling_process_count), fg='blue')
  
      if cloning_process_count:
          utils.secho(colorize, ':: Waiting for {} processes to finish cloning snippets...'.format(cloning_process_count), fg='blue')
  
  
  
  @cli.command(name='ls')
  @click.option('-v', 'verbose', default=True, flag_value='short', help='Provides short listing')
  @click.option(
      '-vv',
      'verbose',
      flag_value='detailed',
      help='Provides the most detailed listing'
  )
  @click.pass_context
  def list_snippets(ctx, verbose):
      """List local snippets"""
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
      data = utils.read_metadata(config)
  
      long_file_list = []
      for item in data['values']:
          snippet = Snippet(config, item)
  
          if not snippet.is_exists():
              msg = '[{}] Snippet does not exist in snippet directory. Please `pull` or `sync`'.format(item['id'])
              utils.secho(colorize, msg, fg='blue')
  
              continue
  
          if verbose == 'short':
              utils.secho(colorize, '[{}] {}'.format(item['id'], item['title']), fg='blue')
  
          elif verbose == 'detailed':
              # Show files in snippet
              onlyfiles = snippet.get_files()
  
              for file_name in onlyfiles:
                  snippet_path = snippet.get_path()
                  long_file_list.append(os.path.join(snippet_path, file_name))
  
      if long_file_list:
          click.echo_via_pager('\n'.join(long_file_list))
  
  
  @cli.command(name='pull')
  @click.pass_context
  def pull_local_snippets(ctx):
      """Update local snippets from Bitbucket.
  
      Pull changes of existing snippets and clone new snippets.
      """
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
  
      api = SnippetApi(config)
      res = api.get_all()
  
      utils.update_metadata(config, res)
  
      for item in res['values']:
          snippet = Snippet(config, item)
  
          if snippet.is_exists():
              utils.secho(colorize, '[{}] Pulling ...'.format(snippet.snippet_id), fg='blue')
              snippet.pull()
              snippet.update_dir_name()
          else:
              utils.secho(colorize, '[{}] Cloning ...'.format(snippet.snippet_id), fg='blue')
              snippet.clone()
  
      utils.secho(colorize, 'Local snippets updated and new snippets downloaded from Bitbucket', fg='blue')
  
  
  def _edit_snippet_file(ctx, param, relative_path):
      """Open snippet file with default editor"""
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
  
      if not relative_path or ctx.resilient_parsing:
          return
  
      file_path = os.path.join(config.get('snipper', 'snippet_dir'), relative_path)
  
      if os.path.exists(file_path):
          click.edit(filename=file_path)
      else:
          utils.secho(colorize, 'File not exist. Exiting ...', fg='red')
  
      ctx.exit()
  
  
  @cli.command(name='edit', help='Edit snippet file')
  @click.option('--fuzzy', is_flag=True, default=True, help='Open fuzzy file finder')
  @click.argument(
      'FILE_PATH',
      type=click.Path(),
      required=False, is_eager=True, expose_value=False,
      callback=_edit_snippet_file
  )
  @click.pass_context
  def edit_snippet_file(ctx, fuzzy, file_path=None):
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
  
      utils.secho(colorize, 'Select a file for edit with fuzzy search.', fg="yellow")
      utils.secho(colorize, 'Let\'s write some text. Press Ctrl+c for quit', fg="yellow")
  
      selected_file = prompt(
          u'> ',
          completer=SnippetFileCompleter(config),
          validator=ValidateSnippetFile(config)
      )
      file_path = os.path.join(config.get('snipper', 'snippet_dir'), selected_file)
  
      click.edit(filename=file_path)
  
      snippet_dir_name, _ = os.path.split(selected_file)
      repo_dir = os.path.join(config.get('snipper', 'snippet_dir'), snippet_dir_name)
  
      commit_message = u"{} updated".format(selected_file)
      Repo.commit(repo_dir, commit_message)
  
      if config.getboolean('snipper', 'auto_push'):
          utils.secho(colorize, 'Pushing changes to Bitbucket', fg='blue')
          Repo.push(repo_dir)
  
  
  @cli.command(name='new', help='Create new snippet from file[s]/STDIN')
  @click.option('--title', '-t', help='Snippet title', default='')
  @click.option('--public', '-p', help='Make snippet public. Private by default', is_flag=True)
  @click.option('--hg', '-hg', is_flag=True, help='Use mercurial. Git by default')
  @click.option('--copy-url', '-c', help='Copy resulting URL to clipboard', is_flag=True)
  @click.option('--open', '-o', help='Open snippet URL on browser after create', is_flag=True)
  @click.option('--paste', '-P', help='Create snippet from clipboard', is_flag=True)
  @click.option('--filename', '-f', help='Used when content read from STDIN or clipboard')
  @click.argument('files', nargs=-1, type=click.Path(exists=True))
  @click.pass_context
  def new_snippet(ctx, files, **kwargs):
  
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
      content_list = []
  
      if files:
          # Read files given as positional parameter
          content_list.extend(utils.open_files(files))
  
      # if filename is not specified, it is exist everytime as None
      # so kwargs.get('filename', default_val) not working
      filename = kwargs.get('filename')
      if not filename:
          filename = config.get('snipper', 'default_filename')
  
      title = kwargs.get('title', None)
  
      # Read from STDIN of clipboard but not both
      if not sys.stdin.isatty():
          # Read from stdin if stdin has some data
          streamed_data = sys.stdin.read()
          content_list.append(('file', (filename, streamed_data,),))
          utils.secho(colorize, 'New file created from STDIN', fg='blue')
      elif kwargs.get('paste'):
          # Read from clipboard
          clipboard_text = pyperclip.paste()
          if clipboard_text:
  
              if not title:
                  # if title not specified, make title first 50 charecters of first line
                  title = clipboard_text.split('\n')[0][:50]
  
              content_list.append(('file', (filename, clipboard_text)))
              utils.secho(colorize, 'New file created from clipboard content', fg='blue')
          else:
              utils.secho(colorize, 'Clipboard is empty, ignoring', fg='yellow')
  
      if not content_list:
  
          # click.edit() return None if user closes to editor without saving.
          content = click.edit()
  
          if content is None:
              utils.secho(colorize, 'Empty content. Exiting', fg='red', err=True)
              sys.exit(1)
  
          if content == '':
              confirm = click.confirm('Content is empty. Create anyway?')
              if not confirm:
                  sys.exit(1)
  
          if not title:
              # if title not specified, pick first 50 charecters of file as title.
              title = content.split('\n')[0][:50]
  
          content_list.append(('file', (filename, content)))
  
      scm = 'hg' if kwargs.get('hg') else 'git'
  
      utils.secho(colorize, 'Snippet creating...', fg='blue')
  
      api = SnippetApi(config)
      response = api.create_snippet(
          content_list,
          not kwargs.get('public', False),
          title,
          scm,
      )
  
      if response.ok:
  
          # Update metadata file
          metadata = utils.read_metadata(config)
          metadata['values'].append(response.json())
          utils.update_metadata(config, metadata)
  
          snippet = Snippet(config, response.json())
          snippet.clone()
  
          utils.secho(colorize, 'Created snippet cloned from Bitbucket', fg='green')
  
          snipper_url = snippet.data['links']['html']['href']
          if kwargs.get('copy_url'):
              # Copy snippet url to clipboard
              pyperclip.copy(snipper_url)
              utils.secho(colorize, 'URL copied to clipboard', fg='green')
  
          if kwargs.get('open'):
              webbrowser.open_new_tab(snipper_url)
  
          print(snippet.get_detail_for_print())
  
  
  @cli.command(name='sync', help='Sync snippets with Bitbucket')
  @click.argument('snippet_id', nargs=1, required=False)
  @click.pass_context
  def sync_snippets(ctx, **kwargs):
      config = ctx.obj
      _sync_snippets(config, **kwargs)
  
  
  @cli.command(name='add', help='Add file[s] to snippet')
  @click.argument('to', nargs=1, required=False)
  @click.argument('files', nargs=-1, type=click.Path(exists=True))
  @click.option('--filename', '-f', help='Used when content read from STDIN or clipboard')
  @click.option('--open', '-o', help='Open snippet URL on browser after file added', is_flag=True)
  @click.option('--paste', '-P', help='Read content from clipboard', is_flag=True)
  @click.option('--copy-url', '-c', help='Copy snippet URL to clipboard', is_flag=True)
  @click.pass_context
  def add_to_snippet(ctx, files, **kwargs):
      config = ctx.obj
      colorize = config.getboolean('snipper', 'colorize')
      selected_snippet_dirname = None
  
      if not kwargs.get('to'):
          utils.secho(colorize, 'Select snippet to add file with fuzzy search.', fg="yellow")
          utils.secho(colorize, 'Let\'s write some text. Press Ctrl+c for quit', fg="yellow")
          selected_snippet_dirname = prompt(
              u'> ',
              completer=SnippetDirCompleter(config),
              validator=ValidateSnippetDir(config)
          )
  
      if not selected_snippet_dirname:
          selected_snippet_dirname = kwargs.get('to', '')
  
      ## older snippets IDs were 5-char and newer IDs are 6-char
      snippet_dir_path_regex = re.search('(?:.*)?([\w]{6})$', selected_snippet_dirname)
      if not snippet_dir_path_regex:
          snippet_dir_path_regex = re.search('(?:.*)?([\w]{5})$', selected_snippet_dirname)
  
      if not snippet_dir_path_regex:
          utils.secho(colorize, 'Give me path of snippet directory or snippet id', fg='red', err=True)
          utils.secho(colorize, 'Existing snippet directories:', fg='blue')
  
          _print_snippet_dirs(config, relative=True)
          sys.exit(1)
  
      snippet_id = snippet_dir_path_regex.group(1)
  
      if not sys.stdin.isatty() and kwargs.get('paste'):
          utils.secho(colorize, 'You cannot use STDIN and clipboard both for creating snippet file.', fg='red', err=True)
          utils.secho(colorize, 'Please pipe content from STDIN or use -P for getting content from clipboard but not both.', fg='red', err=True)
          sys.exit(1)
  
      data = utils.read_metadata(config)
      repo_parent = config.get('snipper', 'snippet_dir')
      snippet = None
  
      for item in data['values']:
          if not snippet_id == item['id']:
              continue
  
          matched_path = glob.glob(os.path.join(repo_parent, '*{}'.format(snippet_id)))
          if not matched_path:
              utils.secho(colorize, '[{}] Snippet directory not found.'.format(snippet_id), fg='red', err=True)
              sys.exit(1)
  
          snippet_dir = matched_path[0]
          snippet = Snippet(config, item)
          break
  
      if not snippet:
          utils.secho(colorize, 'Snippet not found. Exiting!'.format(snippet_id), fg='red', err=True)
          sys.exit(1)
  
      # if filename is not specified, it is exist everytime as None
      # so kwargs.get('filename', default_val) not working
  
      filename = kwargs.get('filename')
      if not filename:
          filename = config.get('snipper', 'default_filename')
  
      file_path = utils.get_incremented_file_path(os.path.join(snippet_dir, filename))
  
      if not sys.stdin.isatty():
          # Read from STDIN
          streamed_data = sys.stdin.read()
  
          with open(file_path, 'w') as file:
              file.write(streamed_data)
              utils.secho(colorize, 'File created from STDIN: {}'.format(file_path), fg='blue')
  
      elif kwargs.get('paste'):
          # Read from clipboard
          clipboard_text = pyperclip.paste()
  
          if clipboard_text:
              with open(file_path, 'w') as file:
                  file.write(streamed_data)
                  utils.secho(colorize, 'File created from clipboard content: {}'.format(file_path), fg='blue')
          else:
              utils.secho(colorize, 'Clipboard is empty, ignoring', fg='yellow')
      elif not files:
          # Open editor
          new_file_content = click.edit()
          if new_file_content is None:
              utils.secho(colorize, 'Empty content. Exiting', fg='red', err=True)
              sys.exit(1)
  
          if new_file_content == '':
              confirm = click.confirm('Content is empty. Create anyway?')
              if not confirm:
                  sys.exit(1)
  
          with open(file_path, 'w') as file:
              file.write(new_file_content)
              utils.secho(colorize, 'File created: {}'.format(file_path), fg='blue')
  
      for file in files:
          # Add given files to the snippet
          shutil.copy(file, snippet_dir)
          utils.secho(colorize, 'File added: {}'.format(file), fg='blue')
  
      snippet.commit()
  
      if config.getboolean('snipper', 'auto_push'):
          utils.secho(colorize, 'Snippet pushing to Bitbucket', fg='blue')
          snippet.push()
  
  
  def _print_snippet_dirs(config, relative=True):
      colorize = config.getboolean('snipper', 'colorize')
      data = utils.read_metadata(config)
  
      for item in data['values']:
          # Show files in snippet
          snippet = Snippet(config, item)
          path = snippet.get_slugified_dirname() if relative else snippet.get_path()
          utils.secho(colorize, path, fg='yellow')
  
  
  if __name__ == '__main__':
      cli()
  