Repository: mkdocs/mkdocs

Issue:
Global template variables?
This is more of a question (and potentially a feature request if this isn't supported) than an issue – is there some way to declare global variables?

For example, in my config.yml, I'd like to define some global variable, `version-num` that, of course, represents the current version of the library that I'm documenting. I'd naturally like to use this all over in my documentation and have only one place to update the version number when releasing a new version – in config.yml.

Though I've tried to do this and beyond the pre-defined variables in the mkdocs documentation, nothing that I declare seems to be picked up.

Is there some way to do this that I don't see?

PS, Jekyll allows for this, so I'm basically assuming the same semantics as Jekyll, i.e., that user-defined variables in config.yml are globally accessible

Discussion:

Patch:
diff --git a/docs/user-guide/configuration.md b/docs/user-guide/configuration.md
index a619e5ea..97824ff0 100644
--- a/docs/user-guide/configuration.md
+++ b/docs/user-guide/configuration.md
@@ -163,6 +163,13 @@ Set a list of JavaScript files to be included by the theme.
 
 **default**: By default `extra_javascript` will contain a list of all the JavaScript files found within the `docs_dir`, if none are found it will be `[]` (an empty list).
 
+
+### extra
+
+A set of key value pairs, where the values can be any valid YAML construct, that will be passed to the template. This allows for great flexibility when creating custom themes.
+
+**default**: By default `extra` will be an empty key value mapping.
+
 ## Preview controls
 
 ### use_directory_urls
diff --git a/docs/user-guide/styling-your-docs.md b/docs/user-guide/styling-your-docs.md
index af7d6ef7..78d510b3 100644
--- a/docs/user-guide/styling-your-docs.md
+++ b/docs/user-guide/styling-your-docs.md
@@ -119,15 +119,11 @@ Article content from each page specified in `mkdocs.yml` is inserted using the `
 
 ### Template Variables
 
-Each template in a theme is built with a template context. These are the variables that are available to theme. The context varies depending on the template that is being built. At the moment templates are either built with
-the global context or with a page specific context. The global context is used
-for HTML pages that don't represent an individual Markdown document, for
-example a 404.html page or search.html.
-
+Each template in a theme is built with a template context. These are the variables that are available to themes. The context varies depending on the template that is being built. At the moment templates are either built with the global context or with a page specific context. The global context is used for HTML pages that don't represent an individual Markdown document, for example a 404.html page or search.html.
 
 #### Global Context
 
-The following variables in the context map directly the the configuration file.
+The following variables in the context map directly the the [configuration options](/user-guide/configuration/).
 
 Variable Name     | Configuration name
 ----------------- | ------------------- |
@@ -140,6 +136,7 @@ repo_name         | repo_name           |
 site_url          | site_url            |
 extra_css         | extra_css           |
 extra_javascript  | extra_javascript    |
+extra             | extra               |
 include_nav       | include_nav         |
 include_next_prev | include_next_prev   |
 copyright         | copyright           |
@@ -237,6 +234,34 @@ The page object for the previous  page. The isage is the same as for
 ##### next_page
 The page object for the next page.The isage is the same as for `current_page`.
 
+#### Extra Context
+
+Additional variables can be passed to the template with the [`extra`](/user-guide/configuration/#extra) configuration option. This is a set of key value pairs that can make custom templates far more flexible.
+
+For example, this could be used to include the project version of all pages and a list of links related to the project. This can be achieved with the following `extra` configuration:
+
+```yaml
+extra:
+    version: 0.13.0
+    links:
+        - https://github.com/mkdocs
+        - https://docs.readthedocs.org/en/latest/builds.html#mkdocs
+        - http://www.mkdocs.org/
+```
+
+And then displayed with this HTML in the custom theme.
+
+```html
+{{ extra.version }}
+
+{% if extra.links %}
+  <ul>
+  {% for link in extra.links %}
+      <li>{{ link }}</li>
+  {% endfor %}
+  </ul>
+{% endif %}
+```
 
 ### Search and themes
 
diff --git a/mkdocs/build.py b/mkdocs/build.py
index 1bd28e88..55dd4d47 100644
--- a/mkdocs/build.py
+++ b/mkdocs/build.py
@@ -1,4 +1,5 @@
 # coding: utf-8
+from __future__ import print_function
 
 from datetime import datetime
 from io import open
@@ -90,7 +91,9 @@ def get_global_context(nav, config):
         'google_analytics': config['google_analytics'],
 
         'mkdocs_version': mkdocs.__version__,
-        'build_date_utc': datetime.utcnow()
+        'build_date_utc': datetime.utcnow(),
+
+        'extra': config['extra']
     }
 
 
@@ -149,7 +152,7 @@ def build_sitemap(config, env, site_navigation):
 
 def build_template(template_name, env, config, site_navigation=None):
 
-    log.debug("Building template: %s", template_name)
+    log.debug("Building %s page", template_name)
 
     try:
         template = env.get_template(template_name)
@@ -279,12 +282,12 @@ def build(config, live_server=False, dump_json=False, clean_site_dir=False):
     Perform a full site build.
     """
     if clean_site_dir:
-        log.info("Cleaning site directory")
+        print("Cleaning site directory")
         utils.clean_directory(config['site_dir'])
     if not live_server:
-        log.info("Building documentation to directory: %s", config['site_dir'])
+        print("Building documentation to directory: %s" % config['site_dir'])
         if not clean_site_dir and site_directory_contains_stale_files(config['site_dir']):
-            log.info("The directory contains stale files. Use --clean to remove them.")
+            print("Directory %s contains stale files. Use --clean to remove them." % config['site_dir'])
 
     if dump_json:
         build_pages(config, dump_json=True)
diff --git a/mkdocs/cli.py b/mkdocs/cli.py
index 91ad8d34..06aa8a86 100644
--- a/mkdocs/cli.py
+++ b/mkdocs/cli.py
@@ -19,16 +19,11 @@ def configure_logging(is_verbose=False):
     '''When a --verbose flag is passed, increase the verbosity of mkdocs'''
 
     logger = logging.getLogger('mkdocs')
-    logger.propagate = False
-    stream = logging.StreamHandler()
-    formatter = logging.Formatter("%(levelname)-7s -  %(message)s ")
-    stream.setFormatter(formatter)
-    logger.addHandler(stream)
 
     if is_verbose:
         logger.setLevel(logging.DEBUG)
     else:
-        logger.setLevel(logging.INFO)
+        logger.setLevel(logging.WARNING)
 
 
 clean_help = "Remove old files from the site_dir before building"
diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py
index b3b14146..6ce0922e 100644
--- a/mkdocs/config/defaults.py
+++ b/mkdocs/config/defaults.py
@@ -96,4 +96,11 @@ DEFAULT_SCHEMA = (
     # enabling strict mode causes MkDocs to stop the build when a problem is
     # encountered rather than display an error.
     ('strict', config_options.Type(bool, default=False)),
+
+    # extra is a mapping/dictionary of data that is passed to the template.
+    # This allows template authors to require extra configuration that not
+    # relevant to all themes and doesn't need to be explicitly supported by
+    # MkDocs itself. A good example here would be including the current
+    # project version.
+    ('extra', config_options.Type(dict, default={})),
 )
diff --git a/mkdocs/gh_deploy.py b/mkdocs/gh_deploy.py
index 4e07a0a5..f5692d2d 100644
--- a/mkdocs/gh_deploy.py
+++ b/mkdocs/gh_deploy.py
@@ -1,19 +1,15 @@
-import logging
+from __future__ import print_function
 import subprocess
 import os
 
-log = logging.getLogger(__name__)
-
 
 def gh_deploy(config):
 
     if not os.path.exists('.git'):
-        log.info('Cannot deploy - this directory does not appear to be a git '
-                 'repository')
+        print('Cannot deploy - this directory does not appear to be a git repository')
         return
 
-    log.info("Copying '%s' to `gh-pages` branch and pushing to GitHub.",
-             config['site_dir'])
+    print("Copying '%s' to `gh-pages` branch and pushing to GitHub." % config['site_dir'])
     try:
         command = ['ghp-import', '-p', config['site_dir']]
         if 'remote_branch' in config:
@@ -27,16 +23,13 @@ def gh_deploy(config):
         # This GitHub pages repository has a CNAME configured.
         with(open('CNAME', 'r')) as f:
             cname_host = f.read().strip()
-        log.info('Based on your CNAME file, your documentation should be '
-                 'available shortly at: http://%s', cname_host)
-        log.info('NOTE: Your DNS records must be configured appropriately for '
-                 'your CNAME URL to work.')
+        print('Based on your CNAME file, your documentation should be available shortly at: http://%s' % cname_host)
+        print('NOTE: Your DNS records must be configured appropriately for your CNAME URL to work.')
         return
 
     # No CNAME found.  We will use the origin URL to determine the GitHub
     # pages location.
-    url = subprocess.check_output(["git", "config", "--get",
-                                   "remote.origin.url"])
+    url = subprocess.check_output(["git", "config", "--get", "remote.origin.url"])
     url = url.decode('utf-8').strip()
 
     host = None
@@ -48,10 +41,10 @@ def gh_deploy(config):
 
     if host is None:
         # This could be a GitHub Enterprise deployment.
-        log.info('Your documentation should be available shortly.')
+        print('Your documentation should be available shortly.')
     else:
         username, repo = path.split('/', 1)
         if repo.endswith('.git'):
             repo = repo[:-len('.git')]
         url = 'http://%s.github.io/%s' % (username, repo)
-        log.info('Your documentation should shortly be available at: ' + url)
+        print('Your documentation should shortly be available at: ' + url)
diff --git a/mkdocs/new.py b/mkdocs/new.py
index 165d124d..4f3420f9 100644
--- a/mkdocs/new.py
+++ b/mkdocs/new.py
@@ -1,8 +1,7 @@
 # coding: utf-8
-from __future__ import unicode_literals
+from __future__ import print_function, unicode_literals
 
 import os
-import logging
 from io import open
 
 config_text = 'site_name: My Docs\n'
@@ -25,8 +24,6 @@ For full documentation visit [mkdocs.org](http://mkdocs.org).
         ...       # Other markdown pages, images and other files.
 """
 
-log = logging.getLogger(__name__)
-
 
 def new(output_dir):
 
@@ -35,20 +32,20 @@ def new(output_dir):
     index_path = os.path.join(docs_dir, 'index.md')
 
     if os.path.exists(config_path):
-        log.info('Project already exists.')
+        print('Project already exists.')
         return
 
     if not os.path.exists(output_dir):
-        log.info('Creating project directory: %s', output_dir)
+        print('Creating project directory: %s' % output_dir)
         os.mkdir(output_dir)
 
-    log.info('Writing config file: %s', config_path)
+    print('Writing config file: %s' % config_path)
     open(config_path, 'w', encoding='utf-8').write(config_text)
 
     if os.path.exists(index_path):
         return
 
-    log.info('Writing initial docs: %s', index_path)
+    print('Writing initial docs: %s' % index_path)
     if not os.path.exists(docs_dir):
         os.mkdir(docs_dir)
     open(index_path, 'w', encoding='utf-8').write(index_text)


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
index 7f2a04a7..96f3b5b0 100644
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -7,6 +7,7 @@ import tempfile
 import unittest
 
 from six.moves import zip
+import mock
 
 from mkdocs import build, nav
 from mkdocs.config import base as config_base, defaults as config_defaults
@@ -343,3 +344,22 @@ class BuildTests(unittest.TestCase):
         """)
 
         self.assertEqual(html.strip(), expected_html)
+
+    def test_extra_context(self):
+
+        # Same as the default schema, but don't verify the docs_dir exists.
+        config = config_base.Config(schema=config_defaults.DEFAULT_SCHEMA)
+        config.load_dict({
+            'site_name': "Site",
+            'extra': {
+                'a': 1
+            }
+        })
+
+        self.assertEqual(config.validate(), ([], []))
+
+        context = build.get_global_context(mock.Mock(), config)
+
+        self.assertEqual(context['extra'], {
+            'a': 1
+        })