# Path: scuba/config.py
# def find_config():
#     '''Search up the directory hierarchy for .scuba.yml
# 
#     Returns: path, rel, config on success, or None if not found
#         path    The absolute path of the directory where .scuba.yml was found
#         rel     The relative path from the directory where .scuba.yml was found
#                 to the current directory
#         config  The loaded configuration
#     '''
#     cross_fs = 'SCUBA_DISCOVERY_ACROSS_FILESYSTEM' in os.environ
#     path = os.getcwd()
# 
#     rel = ''
#     while True:
#         cfg_path = os.path.join(path, SCUBA_YML)
#         if os.path.exists(cfg_path):
#             return path, rel, load_config(cfg_path)
# 
#         if not cross_fs and os.path.ismount(path):
#             msg = '{} not found here or any parent up to mount point {}'.format(SCUBA_YML, path) \
#                    + '\nStopping at filesystem boundary (SCUBA_DISCOVERY_ACROSS_FILESYSTEM not set).'
#             raise ConfigNotFoundError(msg)
# 
#         # Traverse up directory hierarchy
#         path, rest = os.path.split(path)
#         if not rest:
#             raise ConfigNotFoundError('{} not found here or any parent directories'.format(SCUBA_YML))
# 
#         # Accumulate the relative path back to where we started
#         rel = os.path.join(rest, rel)
# 
# class ScubaConfig:
#     def __init__(self, **data):
#         optional_nodes = (
#             'image', 'aliases', 'hooks', 'entrypoint', 'environment', 'shell',
#             'docker_args', 'volumes',
#         )
# 
#         # Check for unrecognized nodes
#         extra = [n for n in data if not n in optional_nodes]
#         if extra:
#             raise ConfigError('{}: Unrecognized node{}: {}'.format(SCUBA_YML,
#                     's' if len(extra) > 1 else '', ', '.join(extra)))
# 
#         self._image = data.get('image')
#         self.shell = data.get('shell', DEFAULT_SHELL)
#         self.entrypoint = _get_entrypoint(data)
#         self.docker_args = _get_docker_args(data)
#         self.volumes = _get_volumes(data)
#         self._load_aliases(data)
#         self._load_hooks(data)
#         self._load_environment(data)
# 
# 
#     def _load_aliases(self, data):
#         self.aliases = {}
# 
#         for name, node in data.get('aliases', {}).items():
#             if ' ' in name:
#                 raise ConfigError('Alias names cannot contain spaces')
#             self.aliases[name] = ScubaAlias.from_dict(name, node)
# 
#     def _load_hooks(self, data):
#         self.hooks = {}
# 
#         for name in ('user', 'root',):
#             node = data.get('hooks', {}).get(name)
#             if node:
#                 hook = _process_script_node(node, name)
#                 self.hooks[name] = hook
# 
#     def _load_environment(self, data):
#         self.environment = _process_environment(data.get('environment'), 'environment')
# 
# 
#     @property
#     def image(self):
#         if not self._image:
#             raise ConfigError("Top-level 'image' not set")
#         return self._image
# 
# class OverrideMixin:
#     '''
#     A mixin class that indicates an instance's value should override something
# 
#     This class is mixed into objects loaded from YAML with an !override tag,
#     and any object can be checked if it is an OverrideMixin using isinstance().
#     '''
#     pass
# 
# Path: scuba/config.py
# class ConfigError(Exception):
#     pass
# 
# class ConfigNotFoundError(ConfigError):
#     pass
# 
# Path: scuba/dockerutil.py
# def get_image_command(image):
#     '''Gets the default command for an image'''
#     info = docker_inspect_or_pull(image)
#     try:
#         return info['Config']['Cmd']
#     except KeyError as ke:
#         raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke))
# 
# Path: scuba/dockerutil.py
# def get_image_entrypoint(image):
#     '''Gets the image entrypoint'''
#     info = docker_inspect_or_pull(image)
#     try:
#         return info['Config']['Entrypoint']
#     except KeyError as ke:
#         raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke))
# 
# Path: scuba/dockerutil.py
# def make_vol_opt(hostdir, contdir, options=None):
#     '''Generate a docker volume option'''
#     vol = '--volume={}:{}'.format(hostdir, contdir)
#     if options != None:
#         if isinstance(options, str):
#             options = (options,)
#         vol += ':' + ','.join(options)
#     return vol
# 
# Path: scuba/utils.py
# def shell_quote_cmd(cmdlist):
#     return ' '.join(map(shell_quote, cmdlist))
# 
# def flatten_list(x):
#     if not isinstance(x, list):
#         raise ValueError("argument is not a list")
#     result = []
#     for i in x:
#         if isinstance(i, list):
#             for j in flatten_list(i):
#                 result.append(j)
#         else:
#             result.append(i)
#     return result
# 
# def get_umask():
#     # Same logic as bash/builtins/umask.def
#     val = os.umask(0o22)
#     os.umask(val)
#     return val
# 
# def writeln(f, line):
#     f.write(line + '\n')


import os
import shutil
import sys
import tempfile
from collections.abc import Mapping
from grp import getgrgid
from io import StringIO
from pwd import getpwuid
from .config import find_config, ScubaConfig, OverrideMixin
from .config import ConfigError, ConfigNotFoundError
from .dockerutil import get_image_command
from .dockerutil import get_image_entrypoint
from .dockerutil import make_vol_opt
from .utils import shell_quote_cmd, flatten_list, get_umask, writeln



class ScubaError(Exception):
    pass

class ScubaDive:

# Path: scuba/config.py
# def find_config():
#     '''Search up the directory hierarchy for .scuba.yml
# 
#     Returns: path, rel, config on success, or None if not found
#         path    The absolute path of the directory where .scuba.yml was found
#         rel     The relative path from the directory where .scuba.yml was found
#                 to the current directory
#         config  The loaded configuration
#     '''
#     cross_fs = 'SCUBA_DISCOVERY_ACROSS_FILESYSTEM' in os.environ
#     path = os.getcwd()
# 
#     rel = ''
#     while True:
#         cfg_path = os.path.join(path, SCUBA_YML)
#         if os.path.exists(cfg_path):
#             return path, rel, load_config(cfg_path)
# 
#         if not cross_fs and os.path.ismount(path):
#             msg = '{} not found here or any parent up to mount point {}'.format(SCUBA_YML, path) \
#                    + '\nStopping at filesystem boundary (SCUBA_DISCOVERY_ACROSS_FILESYSTEM not set).'
#             raise ConfigNotFoundError(msg)
# 
#         # Traverse up directory hierarchy
#         path, rest = os.path.split(path)
#         if not rest:
#             raise ConfigNotFoundError('{} not found here or any parent directories'.format(SCUBA_YML))
# 
#         # Accumulate the relative path back to where we started
#         rel = os.path.join(rest, rel)
# 
# class ScubaConfig:
#     def __init__(self, **data):
#         optional_nodes = (
#             'image', 'aliases', 'hooks', 'entrypoint', 'environment', 'shell',
#             'docker_args', 'volumes',
#         )
# 
#         # Check for unrecognized nodes
#         extra = [n for n in data if not n in optional_nodes]
#         if extra:
#             raise ConfigError('{}: Unrecognized node{}: {}'.format(SCUBA_YML,
#                     's' if len(extra) > 1 else '', ', '.join(extra)))
# 
#         self._image = data.get('image')
#         self.shell = data.get('shell', DEFAULT_SHELL)
#         self.entrypoint = _get_entrypoint(data)
#         self.docker_args = _get_docker_args(data)
#         self.volumes = _get_volumes(data)
#         self._load_aliases(data)
#         self._load_hooks(data)
#         self._load_environment(data)
# 
# 
#     def _load_aliases(self, data):
#         self.aliases = {}
# 
#         for name, node in data.get('aliases', {}).items():
#             if ' ' in name:
#                 raise ConfigError('Alias names cannot contain spaces')
#             self.aliases[name] = ScubaAlias.from_dict(name, node)
# 
#     def _load_hooks(self, data):
#         self.hooks = {}
# 
#         for name in ('user', 'root',):
#             node = data.get('hooks', {}).get(name)
#             if node:
#                 hook = _process_script_node(node, name)
#                 self.hooks[name] = hook
# 
#     def _load_environment(self, data):
#         self.environment = _process_environment(data.get('environment'), 'environment')
# 
# 
#     @property
#     def image(self):
#         if not self._image:
#             raise ConfigError("Top-level 'image' not set")
#         return self._image
# 
# class OverrideMixin:
#     '''
#     A mixin class that indicates an instance's value should override something
# 
#     This class is mixed into objects loaded from YAML with an !override tag,
#     and any object can be checked if it is an OverrideMixin using isinstance().
#     '''
#     pass
# 
# Path: scuba/config.py
# class ConfigError(Exception):
#     pass
# 
# class ConfigNotFoundError(ConfigError):
#     pass
# 
# Path: scuba/dockerutil.py
# def get_image_command(image):
#     '''Gets the default command for an image'''
#     info = docker_inspect_or_pull(image)
#     try:
#         return info['Config']['Cmd']
#     except KeyError as ke:
#         raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke))
# 
# Path: scuba/dockerutil.py
# def get_image_entrypoint(image):
#     '''Gets the image entrypoint'''
#     info = docker_inspect_or_pull(image)
#     try:
#         return info['Config']['Entrypoint']
#     except KeyError as ke:
#         raise DockerError('Failed to inspect image: JSON result missing key {}'.format(ke))
# 
# Path: scuba/dockerutil.py
# def make_vol_opt(hostdir, contdir, options=None):
#     '''Generate a docker volume option'''
#     vol = '--volume={}:{}'.format(hostdir, contdir)
#     if options != None:
#         if isinstance(options, str):
#             options = (options,)
#         vol += ':' + ','.join(options)
#     return vol
# 
# Path: scuba/utils.py
# def shell_quote_cmd(cmdlist):
#     return ' '.join(map(shell_quote, cmdlist))
# 
# def flatten_list(x):
#     if not isinstance(x, list):
#         raise ValueError("argument is not a list")
#     result = []
#     for i in x:
#         if isinstance(i, list):
#             for j in flatten_list(i):
#                 result.append(j)
#         else:
#             result.append(i)
#     return result
# 
# def get_umask():
#     # Same logic as bash/builtins/umask.def
#     val = os.umask(0o22)
#     os.umask(val)
#     return val
# 
# def writeln(f, line):
#     f.write(line + '\n')


# Path: scuba/scuba.py
import os
import shutil
import sys
import tempfile
from collections.abc import Mapping
from grp import getgrgid
from io import StringIO
from pwd import getpwuid
from .config import find_config, ScubaConfig, OverrideMixin
from .config import ConfigError, ConfigNotFoundError
from .dockerutil import get_image_command
from .dockerutil import get_image_entrypoint
from .dockerutil import make_vol_opt
from .utils import shell_quote_cmd, flatten_list, get_umask, writeln




class ScubaError(Exception):
    pass

class ScubaDive:

    def __init__(self, user_command, config, top_path, top_rel,