Ejemplo n.º 1
0
def configify_file_name(path):
  '''
  Given a path, name it and its config file appropriately for our config naming
  scheme. If no config file is necessary, None is used instead. Returns a tuple
  of the new name and the config file name.
  '''

  base, name = os.path.split(path)

  # determine whether our path name is clean or not
  is_clean = not (name.startswith(constants.DOT_CHARACTER) or
      constants.MACHINE_SEPARATOR_CHARACTER in name)

  # always un-hide the name
  name = util.toggle_hidden(name, False)
  config_file_name = None

  if is_clean:
    # add our dot character if the file was originally hidden
    if util.is_hidden(path):
      name = constants.DOT_CHARACTER + name
  else:
    # otherwise, set the config file to the hidden version of the name
    config_file_name = util.toggle_hidden(name, True)

  configified_path = os.path.join(base, name)
  config_file_path = None
  if config_file_name is not None:
    config_file_path = os.path.join(base, config_file_name)

  return (configified_path, config_file_path)
Ejemplo n.º 2
0
def parse_file_config(path, dest):
  '''
  Parse the given file name and return a normalized config.

  Naming Rules
  ----

  * First leading '_' is replaced with a '.'.
  * When split on '@', every part excluding the first is treated as a machine id
    on which to link the given file.
  * Any file named the same as the local file preceded by a '.' is treated as a
    special config file for that local file.
  * Any of these rules may be combined.
  * A config file overrides all name modifiers, no matter what.
  * Any file that contains the '@' character or a leading '_' will need to have
    a compatible name and a corresponding config file to work. The rationale is
    that this is a very uncommon situation, so we don't bother accomodating it.
  '''

  # get the file name for the given path
  raw_name = os.path.basename(path)

  # determine if we need to add a leading dot to the destination, then remove it
  # from the name if present.
  is_hidden = raw_name[0] == constants.DOT_CHARACTER
  if is_hidden:
    raw_name = raw_name[1:]

  # attempt to find machine ids
  parts = raw_name.split(constants.MACHINE_SEPARATOR_CHARACTER)

  # build the final base name
  name = util.toggle_hidden(parts[0], is_hidden)

  # get the remaining machine ids
  machine_ids = parts[1:]

  config = {
    'paths': [name],
    'machines': machine_ids,
  }

  return normalize_file_config(config, dest)
Ejemplo n.º 3
0
def get_config_path(path):
  '''Return the config file name for a given path.'''
  base, name = os.path.split(path)
  hidden_name = util.toggle_hidden(name, True)
  return os.path.join(base, hidden_name)
Ejemplo n.º 4
0
def manage(conf, args):
  '''
  Move a file to the base directory and leave a link pointing to its new
  location in its place.
  '''
  # bail if the file is already a link
  if os.path.islink(args.path):
    raise ValueError('Unable to manage ' + color.cyan(args.path) +
        " since it's already a link!")

  # make sure the path is a descendant of the destination directory
  if not util.is_descendant(args.path, conf['destination']):
    raise ValueError("Unable to manage files that aren't descendants of " +
        'the destination directory (' + color.cyan(conf['destination']) + ')')

  # mark files that aren't direct descendants of the root as such
  unrooted = os.path.dirname(args.path) != conf['destination']

  # get the path of the file if it will be copied into the repo directory
  dest_path = os.path.join(constants.REPO_DIR, os.path.basename(args.path))

  # rename the file as appropriate to to its original name
  dest_path, config_file_path = config.configify_file_name(dest_path)

  # give unrooted files a config file path so they'll go to the correct place
  if unrooted and config_file_path is None:
    config_file_path = util.toggle_hidden(dest_path, True)

  # bail if the file is already managed and we're not overwriting
  dest_exists = os.path.exists(dest_path)
  config_exists = (config_file_path is not None and
      os.path.exists(config_file_path))
  if (dest_exists or config_exists) and not args.force:
    raise ValueError("Can't manage " + color.cyan(args.path) +
        " since it already appears to be managed (use --force to override)")

  # create a file config if necessary
  # replace any existing dest file with a copy of the new one
  util.rm(dest_path, force=True)
  util.cp(args.path, dest_path, recursive=True)

  # replace any existing config file with our new one
  if config_file_path is not None:
    util.rm(config_file_path, force=True)

    # build a config for this file
    file_config = config.normalize_file_config({
      'paths': [args.path],
    }, conf['destination'])

    # create a config file from our config dict
    with open(config_file_path, 'w') as f:
      json.dump(file_config, f, indent=2)

  # create a link to the new location, overwriting the old file
  util.symlink(args.path, dest_path, overwrite=True)

  print(color.cyan(args.path), 'copied and linked')

  # add and commit the file to the repo if --save is specified
  if args.save:
    files = [color.cyan(os.path.basename(dest_path))]
    if config_file_path:
      files.append(color.cyan(os.path.basename(config_file_path)))
    files = files.join(' and ')

    print('Adding', files, 'to the repository...')

    # move us to the current repo directory so all git commands start there
    os.chdir(constants.REPO_DIR)

    # alert the user if we have uncommitted changes (git exits non-0 in this case)
    if git.diff(exit_code=True, quiet=True, _ok_code=(0, 1)).exit_code != 0:
      raise ValueError('The repository has uncommitted changes - the '
        'newly-managed file will have to be added to the repo manually.')

    # add the new files to the staging area
    git.add(dest_path)
    if config_file_path is not None:
      git.add(config_file_path)

    print('Successfully added', files, 'to the repository')
    print('Committing changes...')

    # commit the file to the repository
    commit_message = 'Manage %s' % os.path.basename(args.path)
    git.commit(m=commit_message, quiet=True)

    print('Commit successful!')
    print('Pushing committed changes...')

    # pull any changes down from upstream, then push our new addition
    git.pull(rebase=True, quiet=True)
    git.push(quiet=True)

    print('Push successful!')