"""Configuration system for CherryPy.

Configuration in CherryPy is implemented via dictionaries. Keys are strings
which name the mapped value, which may be of any type.


Architecture
------------

CherryPy Requests are part of an Application, which runs in a global context,
and configuration data may apply to any of those three scopes:

    Global: configuration entries which apply everywhere are stored in
    cherrypy.config.
    
    Application: entries which apply to each mounted application are stored
    on the Application object itself, as 'app.config'. This is a two-level
    dict where each key is a path, or "relative URL" (for example, "/" or
    "/path/to/my/page"), and each value is a config dict. Usually, this
    data is provided in the call to tree.mount(root(), config=conf),
    although you may also use app.merge(conf).
    
    Request: each Request object possesses a single 'Request.config' dict.
    Early in the request process, this dict is populated by merging global
    config entries, Application entries (whose path equals or is a parent
    of Request.path_info), and any config acquired while looking up the
    page handler (see next).


Declaration
-----------

Configuration data may be supplied as a Python dictionary, as a filename,
or as an open file object. When you supply a filename or file, CherryPy
uses Python's builtin ConfigParser; you declare Application config by
writing each path as a section header:

    [/path/to/my/page]
    request.stream = True

To declare global configuration entries, place them in a [global] section.

You may also declare config entries directly on the classes and methods
(page handlers) that make up your CherryPy application via the '_cp_config'
attribute. For example:

    class Demo:
        _cp_config = {'tools.gzip.on': True}
        
        def index(self):
            return "Hello world"
        index.exposed = True
        index._cp_config = {'request.show_tracebacks': False}

Note, however, that this behavior is only guaranteed for the default
dispatcher. Other dispatchers may have different restrictions on where
you can attach _cp_config attributes.


Namespaces
----------

Configuration keys are separated into namespaces by the first "." in the key.
Current namespaces:

    engine:     Controls the 'application engine', including autoreload.
                These can only be declared in the global config.
    tree:       Grafts cherrypy.Application objects onto cherrypy.tree.
                These can only be declared in the global config.
    hooks:      Declares additional request-processing functions.
    log:        Configures the logging for each application.
                These can only be declared in the global or / config.
    request:    Adds attributes to each Request.
    response:   Adds attributes to each Response.
    server:     Controls the default HTTP server via cherrypy.server.
                These can only be declared in the global config.
    tools:      Runs and configures additional request-processing packages.
    wsgi:       Adds WSGI middleware to an Application's "pipeline".
                These can only be declared in the app's root config ("/").
    checker:    Controls the 'checker', which looks for common errors in
                app state (including config) when the engine starts.
                Global config only.

The only key that does not exist in a namespace is the "environment" entry.
This special entry 'imports' other config entries from a template stored in
cherrypy._cpconfig.environments[environment]. It only applies to the global
config, and only when you use cherrypy.config.update.

You can define your own namespaces to be called at the Global, Application,
or Request level, by adding a named handler to cherrypy.config.namespaces,
app.namespaces, or app.request_class.namespaces. The name can
be any string, and the handler must be either a callable or a (Python 2.5
style) context manager.
"""

try:
    set
except NameError:
    from sets import Set as set

import cherrypy
from cherrypy.lib import reprconf

# Deprecated in  CherryPy 3.2--remove in 3.3
NamespaceSet = reprconf.NamespaceSet

def merge(base, other):
    """Merge one app config (from a dict, file, or filename) into another.
    
    If the given config is a filename, it will be appended to
    the list of files to monitor for "autoreload" changes.
    """
    if isinstance(other, basestring):
        cherrypy.engine.autoreload.files.add(other)
    
    # Load other into base
    for section, value_map in reprconf.as_dict(other).items():
        if not isinstance(value_map, dict):
            raise ValueError(
                "Application config must include section headers, but the "
                "config you tried to merge doesn't have any sections. "
                "Wrap your config in another dict with paths as section "
                "headers, for example: {'/': config}.")
        base.setdefault(section, {}).update(value_map)


class Config(reprconf.Config):
    """The 'global' configuration data for the entire CherryPy process."""

    def update(self, config):
        """Update self from a dict, file or filename."""
        if isinstance(config, basestring):
            # Filename
            cherrypy.engine.autoreload.files.add(config)
        reprconf.Config.update(self, config)

    def _apply(self, config):
        """Update self from a dict."""
        if isinstance(config.get("global", None), dict):
            if len(config) > 1:
                cherrypy.checker.global_config_contained_paths = True
            config = config["global"]
        if 'tools.staticdir.dir' in config:
            config['tools.staticdir.section'] = "global"
        reprconf.Config._apply(self, config)
    
    def __call__(self, *args, **kwargs):
        """Decorator for page handlers to set _cp_config."""
        if args:
            raise TypeError(
                "The cherrypy.config decorator does not accept positional "
                "arguments; you must use keyword arguments.")
        def tool_decorator(f):
            if not hasattr(f, "_cp_config"):
                f._cp_config = {}
            for k, v in kwargs.items():
                f._cp_config[k] = v
            return f
        return tool_decorator


Config.environments = environments = {
    "staging": {
        'engine.autoreload_on': False,
        'checker.on': False,
        'tools.log_headers.on': False,
        'request.show_tracebacks': False,
        'request.show_mismatched_params': False,
        },
    "production": {
        'engine.autoreload_on': False,
        'checker.on': False,
        'tools.log_headers.on': False,
        'request.show_tracebacks': False,
        'request.show_mismatched_params': False,
        'log.screen': False,
        },
    "embedded": {
        # For use with CherryPy embedded in another deployment stack.
        'engine.autoreload_on': False,
        'checker.on': False,
        'tools.log_headers.on': False,
        'request.show_tracebacks': False,
        'request.show_mismatched_params': False,
        'log.screen': False,
        'engine.SIGHUP': None,
        'engine.SIGTERM': None,
        },
    "test_suite": {
        'engine.autoreload_on': False,
        'checker.on': False,
        'tools.log_headers.on': False,
        'request.show_tracebacks': True,
        'request.show_mismatched_params': True,
        'log.screen': False,
        },
    }


def _server_namespace_handler(k, v):
    """Config handler for the "server" namespace."""
    atoms = k.split(".", 1)
    if len(atoms) > 1:
        # Special-case config keys of the form 'server.servername.socket_port'
        # to configure additional HTTP servers.
        if not hasattr(cherrypy, "servers"):
            cherrypy.servers = {}
        
        servername, k = atoms
        if servername not in cherrypy.servers:
            from cherrypy import _cpserver
            cherrypy.servers[servername] = _cpserver.Server()
            # On by default, but 'on = False' can unsubscribe it (see below).
            cherrypy.servers[servername].subscribe()
        
        if k == 'on':
            if v:
                cherrypy.servers[servername].subscribe()
            else:
                cherrypy.servers[servername].unsubscribe()
        else:
            setattr(cherrypy.servers[servername], k, v)
    else:
        setattr(cherrypy.server, k, v)
Config.namespaces["server"] = _server_namespace_handler

def _engine_namespace_handler(k, v):
    """Backward compatibility handler for the "engine" namespace."""
    engine = cherrypy.engine
    if k == 'autoreload_on':
        if v:
            engine.autoreload.subscribe()
        else:
            engine.autoreload.unsubscribe()
    elif k == 'autoreload_frequency':
        engine.autoreload.frequency = v
    elif k == 'autoreload_match':
        engine.autoreload.match = v
    elif k == 'reload_files':
        engine.autoreload.files = set(v)
    elif k == 'deadlock_poll_freq':
        engine.timeout_monitor.frequency = v
    elif k == 'SIGHUP':
        engine.listeners['SIGHUP'] = set([v])
    elif k == 'SIGTERM':
        engine.listeners['SIGTERM'] = set([v])
    elif "." in k:
        plugin, attrname = k.split(".", 1)
        plugin = getattr(engine, plugin)
        if attrname == 'on':
            if v and hasattr(getattr(plugin, 'subscribe', None), '__call__'):
                plugin.subscribe()
                return
            elif (not v) and hasattr(getattr(plugin, 'unsubscribe', None), '__call__'):
                plugin.unsubscribe()
                return
        setattr(plugin, attrname, v)
    else:
        setattr(engine, k, v)
Config.namespaces["engine"] = _engine_namespace_handler


def _tree_namespace_handler(k, v):
    """Namespace handler for the 'tree' config namespace."""
    cherrypy.tree.graft(v, v.script_name)
    cherrypy.engine.log("Mounted: %s on %s" % (v, v.script_name or "/"))
Config.namespaces["tree"] = _tree_namespace_handler


