def find_ros_root(override=None):
    if override is not None:
        if is_ros_root(override):
            return os.path.realpath(override)
        return None
    if "ROS_ROOT" in os.environ:
        rosrootdir = os.environ["ROS_ROOT"]
        if os.path.isdir(rosrootdir):
            rosdir = os.path.normpath(os.path.join(rosrootdir, os.pardir, os.pardir))
            if is_ros_root(rosdir):
                return os.path.realpath(rosdir)
    if "ROS_PACKAGE_PATH" in os.environ:
        candidates = os.environ["ROS_PACKAGE_PATH"].split(os.pathsep)
        candidates.reverse()
        for path in candidates:
            rosdir = os.path.normpath(os.path.join(path, os.pardir))
            if is_ros_root(rosdir):
                return os.path.realpath(rosdir)
    if os.path.islink("/opt/ros/current"):
        rosdir = os.path.join("/opt/ros", os.readlink("/opt/ros/current"))
        if is_ros_root(rosdir):
            return os.path.realpath(rosdir)
    return None def get_workspace_location(override):
    from . import __version__
    wsdir = find_workspace(override)
    if wsdir is not None:
        wstype, wsversion = detect_workspace_type(wsdir)
        if wstype == 3:
            return wsdir
        msg("catkin workspace detected in @{cf}%s@|\n\n" % escape(wsdir))
        if wstype == -1:
            msg(
                "I found a catkin workspace, but %(error_msg)s\n\n"
                "You can delete any corrupted settings and reinitialize the "
                "workspace for rosrepo with the command\n\n"
                "    @!rosrepo init --reset %(path)s@|\n\n"
                % {"path": escape(wsdir), "error_msg": wsversion}
            )
        if wstype == 0:
            msg(
                "I found a catkin workspace, but it is not configured with rosrepo.\n\n"
                "If you wish to use rosrepo with this workspace, run the command\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                % {"path": escape(wsdir)}
            )
        if wstype == 4:
            msg(
                "This catkin workspace has been configured by a newer version of rosrepo, "
                "please upgrade to version @{cf}%(new_version)s@| or newer.\n\n"
                "If you want to revert the workspace back to this version (@{cf}%(old_version)s@|), "
                "you can reset all settings with\n\n"
                "    @!rosrepo init --reset %(path)s@|\n\n"
                "@!@{yf}WARNING@|: Please make a backup before doing this!\n\n"
                % {"path": escape(wsdir), "new_version": escape(wsversion), "old_version": __version__}
            )
        if wstype == 1 or wstype == 2:
            msg(
                "This catkin workspace has been configured by rosrepo @{cf}%(old_version)s@|, "
                "but you are currently running version @{cf}%(new_version)s@|\n\n"
                "If you wish to use the new version of rosrepo, you need to reinitialize the "
                "workspace with the command\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                % {"old_version": escape(wsversion), "new_version": __version__, "path": escape(wsdir)}
            )
        if override is None:
            msg(
                "If this is not the workspace location you were looking for, try "
                "the @{cf}--workspace@| option to override the automatic detection.\n\n"
            )
    else:
        if override is not None:
            msg(
                "There is no catkin workspace in %(path)s\n\n"
                "You can create a new workspace there by running\n\n"
                "    @!rosrepo init %(path)s@|\n\n"
                "If you are really sure that there is a workspace there already, "
                "it is possible that the marker file has been deleted by accident. "
                "In that case, the above command will restore your workspace.\n\n"
                % {"path": escape(override)}
            )
        else:
            msg(
                "I cannot find any catkin workspace.\n\n"
                "Please make sure that you have sourced the @{cf}setup.bash@| file of "
                "your workspace or use the @{cf}--workspace@| option to override the "
                "automatic detection. If you have never created a workspace yet, "
                "you can initialize one in your home directory with\n\n"
                "    @!rosrepo init %s/ros@|\n\n"
                % escape(os.path.expanduser("~"))
            )
    fatal("valid workspace location required\n") def acquire_gitlab_private_token(label, url, credentials_callback=ask_username_and_password):
    global _cached_tokens
    if url in _cached_tokens:
        return _cached_tokens[url]
    retries = 3
    while retries > 0:
        retries -= 1
        login, passwd = credentials_callback("%s [%s]" % (label, url))
        r = requests.post(urljoin(url, "api/v4/session"), data={"login": login, "password": passwd})
        if r.status_code == 404:
            msg("This version of the Gitlab server will not create access tokens via API. "
                "You need to create a personal access token with @!api@| scope manually "
                "and pass it to resrepo with the --private-token option. Visit the following URL:\n\n@{cf}%s/profile/personal_access_tokens@|\n\n" % url)
            fatal("required feature unavailabe")
        if r.status_code == 401:
            msg("@!@{rf}Access denied@|\n", fd=sys.stderr)
            continue
        break
    r.raise_for_status()
    token = r.json()["private_token"]
    _cached_tokens[url] = token
    return token def get_gitlab_projects(wsdir, config, cache=None, offline_mode=False, force_update=False, verbose=True):
    if "gitlab_servers" not in config:
        return []
    gitlab_projects = []
    for gitlab_cfg in config["gitlab_servers"]:
        label = gitlab_cfg.get("label", None)
        url = gitlab_cfg.get("url", None)
        private_token = gitlab_cfg.get("private_token", None)
        if url is not None and private_token is None and not offline_mode:
            warning("not updating '%s': no personal access token configured\n" % url)
            msg("Please visit @{cf}%s/profile/personal_access_tokens@| to create your token and configure it with\n\n    @!rosrepo config --gitlab-login %s --private-token TOKEN@|\n\n" % (url, label))
            # private_token = ask_personal_access_token(url) or None
        gitlab_projects += find_available_gitlab_projects(label, url, private_token=private_token, cache=cache, cache_only=offline_mode, crawl_depth=gitlab_cfg.get("crawl_depth", config.get("gitlab_crawl_depth", 1)), force_update=force_update, verbose=verbose)
    return gitlab_projects class Cache(object):

    def __init__(self, wsdir):
        self.cache_dir = os.path.join(wsdir, ".rosrepo", "cache")
        self.preloaded = {}

    def get_object(self, name, version, default=None):
        if name in self.preloaded:
            if self.preloaded[name].version == version:
                return self.preloaded[name].obj
            return default
        try:
            with open(os.path.join(self.cache_dir, name), "rb") as f:
                cache_file = pickle.loads(zlib.decompress(f.read()))
        except Exception:
            return default
        if not isinstance(cache_file, CacheFile):
            return default
        self.preloaded[name] = cache_file
        if cache_file.version != version:
            return default
        return cache_file.obj

    def set_object(self, name, version, obj):
        cache_file = CacheFile(version=version, obj=obj)
        makedirs(self.cache_dir)
        filepath = os.path.join(self.cache_dir, name)
        write_atomic(filepath, zlib.compress(pickle.dumps(cache_file, -1)), ignore_fail=True)
        self.preloaded[name] = cache_file

    def reset_object(self, name):
        if name in self.preloaded:
            del self.preloaded[name]
        try:
            os.unlink(os.path.join(self.cache_dir, name))
        except OSError:
            pass class Config(object):
    def __init__(self, wsdir, read_only=False):
        self.config_dir = os.path.join(wsdir, ".rosrepo")
        self.config_file = os.path.join(self.config_dir, "config")
        self.read_only = read_only
        if os.path.isfile(self.config_file):
            try:
                with open(self.config_file, "rb") as f:
                    self._data = yaml_load(f)
            except (OSError, IOError, YAMLError):
                raise ConfigError("unreadable configuration file")
            if not isinstance(self._data, dict):
                raise ConfigError("invalid configuration file")
            if "version" not in self._data:
                raise ConfigError("missing configuration version number")
            current = Version(__version__)
            try:
                stored = Version(self._data["version"])
            except (TypeError, ValueError):
                raise ConfigError("invalid configuration version number")
            if stored < current and not read_only:
                self._migrate(stored)
            if stored.version[:2] > current.version[:2] and not read_only:
                raise ConfigError("workspace was configured by newer version of rosrepo")
        else:
            self._data = {"version": __version__}

    def write(self):
        if self.read_only:
            raise ConfigError("cannot write config file marked as read only")
        try:
            makedirs(self.config_dir)
            write_atomic(self.config_file, yaml_dump(self._data, encoding="UTF-8", default_flow_style=False))
        except (IOError, OSError):
            raise ConfigError("cannnot write config file %s" % self.config_file)

    def _migrate(self, old_version):
        if old_version <= Version("3.0.25"):
            if "crawl_depth" in self._data:
                self._data["gitlab_crawl_depth"] = self._data["crawl_depth"]
                del self._data["crawl_depth"]
        self._data["version"] = __version__
        try:
            self.write()
        except ConfigError:
            warning("cannot write updated configuration\n")

    def set_default(self, key, value):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        if key not in self._data:
            self._data[key] = value

    def get(self, key, default=None):
        return self._data.get(key, default)

    def __getitem__(self, key):
        return self._data[key]

    def __setitem__(self, key, value):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        self._data[key] = value

    def __delitem__(self, key):
        if self.read_only:
            raise ConfigError("cannot change read-only configuration")
        if key in self._data:
            del self._data[key]

    def __iter__(self):
        return self._data.__iter__()

    def __len__(self):
        return len(self._data)

    def __contains__(self, key):
        return key in self._data class TableView(object):
    def __init__(self, *args, **kwargs):
        self.columns = args
        self.expand = kwargs.get("expand", False)
        if not self.columns:
            self.width = [1, 1]
        else:
            self.width = [max(1, len(remove_ansi(color_fmt(c)))) for c in self.columns]
        self.rows = []

    def add_row(self, *args):
        row = [r if isinstance(r, list) or isinstance(r, tuple) else (r,) for r in args]
        row = [r if isinstance(r, tuple) or len(r) > 0 else [""] for r in row]  # Handle special case with empty lists
        assert len(row) == len(self.width)
        self.rows.append(row)
        self.width = [max(w, *(len(remove_ansi(color_fmt(r))) for r in rs)) for w, rs in zip(self.width, row)]

    def add_separator(self):
        self.rows.append(None)

    def empty(self):
        return len(self.rows) == 0

    def sort(self, column_index):
        self.rows.sort(key=lambda x: x[column_index])

    def write(self, fd=None, use_color=None):
        width = self.width
        actual_width = sum(width) + 3 * len(width) - 1
        try:
            total_width = get_terminal_size()[0]
        except OSError:
            total_width = None
        if fd is None:
            fd = sys.stdout
        if use_color is None:
            use_color = isatty(fd)
        if total_width is not None:
            if self.expand and actual_width < total_width:
                width[-1] += total_width - actual_width
                actual_width = total_width
            while actual_width > total_width:
                max_width = max(width)
                if max_width == 1:
                    break
                for i in range(len(width)):
                    if width[i] == max_width:
                        width[i] -= 1
                        actual_width -= 1
                        break
        if self.columns:
            fmt = color_fmt(" " + (" @{pf}" + HSEP + "@| ").join(["%s"] * len(width)) + "\n", use_color=use_color)
            fd.write(color_fmt("@{pf}" + HTHICK + THEAD.join([HTHICK * w for w in width]) + HTHICK + "\n", use_color=use_color))
            sep = color_fmt("@{pf}" + HTHIN + XSEP.join([HTHIN * w for w in width]) + HTHIN + "\n", use_color=use_color)
            end = color_fmt("@{pf}" + HTHICK + TFOOT.join([HTHICK * w for w in width]) + HTHICK + "\n", use_color=use_color)
            fd.write(fmt % tuple(pad_ansi_text(color_fmt("@!%s" % c, use_color=use_color), w) for c, w in zip(self.columns, width)))
        else:
            fmt = " %s   %s\n"
            sep = color_fmt("@{pf}" + (HTHIN * actual_width) + "@|\n", use_color=use_color)
            end = sep
        fd.write(sep)
        for row in self.rows:
            if row is None:
                fd.write(sep)
                continue
            for line in zip_longest(*row, fillvalue=""):
                chunks = (slice_ansi_text(color_fmt(r, use_color=use_color), w) for r, w in zip(line, width))
                for chunk in zip_longest(*chunks):
                    fd.write(fmt % tuple(textify(r, fd=fd) if r is not None else " " * w for r, w in zip(chunk, width)))
        fd.write(end) def msg(text, max_width=None, use_color=None, wrap=True, fd=None, **wrap_args):
    from .terminal_color import ansi
    if fd is None:
        fd = sys.stderr
    if use_color is None:
        use_color = isatty(fd)
    ansi_text = color_fmt(text, use_color=use_color)
    if wrap:
        try:
            if max_width is None:
                max_width, _ = get_terminal_size()
        except OSError:
            pass
    fd.write(wrap_ansi_text(ansi_text, max_width, **wrap_args) + (ansi('reset') if use_color else "")) def warning(text, use_color=None):
    prog = "rosrepo"
    msg("@!@{yf}%s: warning: %s" % (prog, text), use_color=use_color, indent_next=len(prog) + 11) def fatal(text):
    raise UserError(text) def escape(msg):
    return msg.replace("@", "@@") DEFAULT_CMAKE_ARGS = [
    "-DCMAKE_BUILD_TYPE=RelWithDebInfo",
] def update_default_git_ignore():
    config_home = os.environ.get("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config"))
    gitconfig_dir = os.path.join(config_home, "git")
    gitignore_file = os.path.join(gitconfig_dir, "ignore")
    gitignore = set()
    try:
        with open(gitignore_file, "r") as f:
            gitignore = set([e for e in f.read().split("\n") if e])
    except IOError:
        pass
    if not DEFAULT_GIT_IGNORE.issubset(gitignore):
        gitignore |= set(DEFAULT_GIT_IGNORE)
        try:
            makedirs(gitconfig_dir)
            with open(gitignore_file, "w") as f:
                f.write("\n".join(sorted(list(gitignore))))
        except (OSError, IOError):
            pass def get_c_compiler(s):
    global COMPILER_LIST
    for compiler in COMPILER_LIST:
        m = re.match(r"^(.*)\b(?:%s)\b(.*)$" % compiler[0], s, re.IGNORECASE)
        if m is not None:
            return "%s%s%s" % (m.group(1), compiler[1], m.group(2))
    return None def get_cxx_compiler(s):
    global COMPILER_LIST
    for compiler in COMPILER_LIST:
        m = re.match(r"^(.*)\b(?:%s)\b(.*)$" % compiler[0], s, re.IGNORECASE)
        if m is not None:
            return "%s%s%s" % (m.group(1), compiler[2], m.group(2))
    return None def call_process(args, bufsize=0, stdin=None, stdout=None, stderr=None, cwd=None, env=None, input_data=None):
    p = Popen(args, bufsize=bufsize, stdin=stdin, stdout=stdout, stderr=stderr, cwd=cwd, env=env)
    if stdin == PIPE or stdout == PIPE or stderr == PIPE:
        stdoutdata, stderrdata = p.communicate(input_data.encode("UTF-8") if input_data else None)
        return p.returncode, stdoutdata.decode("UTF-8") if stdoutdata is not None else None, stderrdata.decode("UTF-8") if stderrdata is not None else None
    else:
        p.wait()
    return p.returncode
import sys
import requests
import platform
    from urlparse import urljoin
    from urllib.parse import urljoin
from .workspace import find_ros_root, get_workspace_location
from .gitlab import acquire_gitlab_private_token, get_gitlab_projects
from .cache import Cache
from .config import Config
from .ui import TableView, msg, warning, fatal, escape
from .common import DEFAULT_CMAKE_ARGS, update_default_git_ignore, get_c_compiler, get_cxx_compiler
from .util import call_process
    from urlparse import urlsplit, urlunsplit
    from urllib.parse import urlsplit, urlunsplit


try:
except ImportError:


def show_config(config):
    table = TableView(expand=True)
    srv_list = []
    for srv in config.get("gitlab_servers", []):
        label = srv.get("label", "")
        url = srv.get("url", "")
        srv_list.append("@{yf}%s%s" % (escape(label) + ": " if label else "", escape(url)))
    table.add_row("@{cf}Gitlab Servers:", srv_list)
    if "store_credentials" in config:
        table.add_row("@{cf}Store Credentials:", "@{yf}" + ("Yes" if config["store_credentials"] else "No"))
    if "git_default_transport" in config:
        table.add_row("@{cf}Default Transport:", "@{yf}%s" % config["git_default_transport"])
    if "gitlab_crawl_depth" in config:
        table.add_row("@{cf}Crawl Depth:", "@{yf}%s" % config["gitlab_crawl_depth"])
    table.add_separator()
    if "ros_root" in config:
        table.add_row("@{cf}Override ROS Path:", "@{yf}" + escape(config["ros_root"]))
    if "compiler" in config:
        table.add_row("@{cf}Compiler:", "@{yf}" + escape(config["compiler"]))
    jobs = config.get("job_limit", None)
    table.add_row("@{cf}Parallel Build Jobs:", "@{yf}" + ("%d" % jobs if jobs is not None else "Unlimited"))
    if "install" in config:
        table.add_row("@{cf}Install:", "@{yf}" + ("Yes" if config["install"] else "No"))
    table.add_row("@{cf}Run catkin_lint:", "@{yf}" + ("Yes" if config["use_catkin_lint"] else "No"))
    table.add_row("@{cf}Skip catkin_lint:", ["@{yf}" + escape(s) for s in config.get("skip_catkin_lint", [])])
    table.add_row("@{cf}Run rosclipse:", "@{yf}" + ("Yes" if config["use_rosclipse"] else "No"))
    table.add_row("@{cf}Offline Mode:", "@{yf}" + ("Yes" if config.get("offline_mode", False) else "No"))
    table.add_separator()
    table.add_row("@{cf}Pinned Packages:", ["@{yf}" + escape(s) for s in config.get("pinned_build", [])])
    table.add_row("@{cf}Default Build:", ["@{yf}" + escape(s) for s in config.get("default_build", [])])
    table.write()


def run(args):
    wsdir = get_workspace_location(args.workspace)
    config = Config(wsdir)

    if args.get_gitlab_url:
        servers = config.get("gitlab_servers", [])
        sys.stdout.write("\n".join([s.get("url", "") for s in servers if s.get("label", "") == args.get_gitlab_url]) + "\n")
        return 0
    if args.show_gitlab_urls:
        servers = config.get("gitlab_servers", [])
        if args.autocomplete:
            sys.stdout.write("\n".join([s.get("label", "") for s in servers]) + "\n")
            return 0
        table = TableView("Label", "Gitlab URL", "Credentials")
        for srv in servers:
            table.add_row(escape(srv.get("label", "")), escape(srv.get("url", "")), "@{gf}yes" if srv.get("private_token", None) is not None else "@{rf}no")
        table.write(fd=sys.stdout)
        return 0

    need_clean = False

    if args.set_ros_root:
        old_ros_root = find_ros_root(config.get("ros_root"))
        new_ros_root = find_ros_root(args.set_ros_root)
        need_clean = old_ros_root != new_ros_root
        config["ros_root"] = args.set_ros_root
    if args.unset_ros_root:
        old_ros_root = find_ros_root(config.get("ros_root"))
        new_ros_root = find_ros_root(None)
        need_clean = old_ros_root != new_ros_root
        del config["ros_root"]

    if args.offline is not None:
        config["offline_mode"] = args.offline
    else:
        args.offline = config.get("offline_mode", False)

    config.set_default("store_credentials", True)
    if args.store_credentials is not None:
        config["store_credentials"] = args.store_credentials

    if args.gitlab_logout:
        config.set_default("gitlab_servers", [])
        for srv in config["gitlab_servers"]:
            if srv.get("label", None) == args.gitlab_logout:
                if "private_token" in srv:
                    del srv["private_token"]
                    msg("Private token removed\n")
                break
        else:
            fatal("no such Gitlab server\n")
    if args.remove_credentials:
        config.set_default("gitlab_servers", [])
        for srv in config["gitlab_servers"]:
            if "private_token" in srv:
                del srv["private_token"]
        msg("All Gitlab private tokens removed")
    if args.gitlab_login:
        config.set_default("gitlab_servers", [])
        for srv in config["gitlab_servers"]:
            label = srv.get("label", None)
            if label == args.gitlab_login:
                url = srv.get("url", None)
                if url is None:
                    fatal("cannot acquire token for Gitlab server without URL\n")
                if args.private_token:
                    private_token = args.private_token
                else:
                    private_token = None
                    if "private_token" in srv:
                        private_token = srv["private_token"]
                        if not args.offline:
                            r = requests.get(urljoin(url, "api/v4/projects"), headers={"PRIVATE-TOKEN": private_token})
                            if r.status_code == 401:
                                private_token = None
                            else:
                                msg("stored Gitlab private token is valid\n")
                        else:
                            warning("cannot verify Gitlab private token in offline mode\n")
                    if private_token is None:
                        if args.offline:

                            fatal("cannot acquire Gitlab private token in offline mode\n")