# Part of the A-A-P recipe executive: Generic Version Control 

# Copyright (C) 2002 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

#
# Functions to get files out of a version control system and put them back.
# Most of the work is directed to one of the modules for a specific version
# control system.
# Uploading/downloading is done by functions in Remote.py.
#

import os
import string

from Process import recipe_error
from Cvs import *
from Work import getwork
from Node import Node


def separate_scheme(url):
    """Isolate the scheme of the URL."""
    # TODO: how about "file:~user"?
    i = string.find(url, "://")
    if i < 0:
	raise UserError, _('No :// found in "%s"') % url
    scheme = string.lower(url[:i])
    for c in scheme:
	if not c in string.lowercase:
	    raise UserError, _('Illegal character before colon in "%s"') % url
    return scheme, url[i + 3:]


def verscont_command(globals, dict, node, use_cache, action):
    """Try performing "action" on node "node" from the semi-URL "dict["name"]".
       May Use a cached file when "use_cache" is non-zero.
       Returns non-zero when it succeeds."""
    scheme, name = separate_scheme(dict["name"])

    # Handle a scheme for which there is a scheme_command() function.
    if globals.has_key(scheme + "_command"):
	return globals[scheme + "_command"](name, dict, [ node ], command)

    # Handle the "cvs://" scheme.
    if scheme == "cvs":
	return cvs_command(name, dict, [ node ], action)

    # Assume it's a normal URL, try downloading/uploading/removing the file.
    if action in ["refresh", "checkout"]:
	return download_file(globals, dict, node, use_cache)
    if action in ["commit", "checkin", "publish", "add"]:
	return upload_file(globals)
    if action == "remove":
	return remote_remove(globals)

    return 0


def handle_node(rpstack, globals, node, use_cache, action, attrnames):
    """Common code for refreshing, committing, etc.
       "action" is the name  of the command: "refresh", "commit", etc.
       "attrnames" is a list of attribute names that can be used.
       """
    # Use the first attribute that exists and isn't empty.
    attr = ''
    for n in attrnames:
	if node.attributes.has_key(n):
	    attr = node.attributes[n]
	    if attr:
		break
    if not attr:
	recipe_error(rpstack, _("Missing %s attribute for %s")
					   % (attrnames[0], node.short_name()))

    # Replace all occurences of "%file%" with the node name (relative to the
    # recipe it was specified in).
    while 1:
	i = string.find(attr, "%file%")
	if i < 0:
	    break
	attr = attr[:i] + node.name + attr[i+6:]

    from Dictlist import string2dictlist
    list = string2dictlist(rpstack, attr)
    if not list:
	recipe_error(rpstack, _("%s attribute for %s is empty")
					       % (attrname, node.short_name()))

    # Loop over the list of refresh locations.  Quit as soon as refreshing
    # worked.
    for org in list:
	# Try downloading this node, return when it worked
	if verscont_command(globals, org, node, use_cache, action):
	    return 1
    return 0


def refresh_node(rpstack, globals, node, use_cache):
    """Refresh "node" according to its "refresh" attribute.
       When there is no "refresh" attribute use "commit".
       Only use cached files when "use_cache" is non-zero.
       Return non-zero for success."""
    return handle_node(rpstack, globals, node, use_cache,
					    "refresh", [ "refresh", "commit" ])


def verscont_node(rpstack, globals, node, action):
    """Checkout "node" according to its "commit" attribute.
       When there is no "commit" attribute use "refresh".
       Return non-zero for success."""
    return handle_node(rpstack, globals, node, 0, action, [ "commit" ])


def publish_node(rpstack, globals, node):
    """Publish "node" according to its "publish" attribute.
       When there is no "publish" attribute use "commit".
       Return non-zero for success."""
    return handle_node(rpstack, globals, node, 0,
					    "publish", [ "publish", "commit" ])


def verscont_removeall(rpstack, globals, dir, recursive):
    """Remove all files in directory "dir" of VCS that don't belong there.
       "dir" is a dictionary for the directory and its attributes.
       Enter directories recursively when "recursive" is non-zero.
       """
    if not dir.has_key("commit"):
	recipe_error(rpstack, _("no commit attribute for %s") % dir["name"])

    from Dictlist import string2dictlist
    commit_list = string2dictlist(rpstack, dir["commit"])
    if not commit_list:
	recipe_error(rpstack, _("commit attribute for %s is empty")
								 % dir["name"])

    dirname = os.path.abspath(dir["name"])
    list = None
    for commit_item in commit_list:
	scheme, name = separate_scheme(commit_item["name"])

	# Handle a scheme for which there is a scheme_list() function.
	if globals.has_key(scheme + "_list"):
	    list = globals[scheme + "_list"](name,
					       commit_item, dirname, recursive)
	    break

	# Handle the "cvs://" scheme.
	if scheme == "cvs":
	    list = cvs_list(name, commit_item, dirname, recursive)
	    break

    if list is None:
	recipe_error(rpstack, _("No working item in commit attribute for %s")
								 % dir["name"])

    # Loop over all items found in the VCS.
    work = getwork(globals)
    for item in list:
	node = work.find_node(item)
	if not node or not node.attributes.has_key("commit"):
	    if not node:
		node = Node(item)
	    # Do something with errors?
	    verscont_command(globals, commit_item, node, 0, "remove")

# vim: set sw=4 sts=4 tw=79 fo+=l:
