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:
I know it _used_ to be possible to set custom variables in the config file and use them in templates, but I think that was removed a while back. Custom variables that only templates can use aren't super-useful though.

I think mkdocs recently added support for using Markdown extensions. Maybe there's an extension that does something like what you want? https://pythonhosted.org/Markdown/extensions/

Thanks for your response.

There's a "meta data" plugin that'd make it so I'd have to manually add meta data to each individual markdown file, which defeats the purpose of globally defining something like a project title or version number. So, upon first glance, no, it doesn't seem like there's an extension I can use.

> Custom variables that only templates can use aren't super-useful though.

I don't understand how this can be true. Could you explain what you mean? I provided one **very** useful example _especially if you're documenting a library_ – version numbers for the library that I'm documenting. I'd much rather write `{{ site.version-number }}` in one place than have to grep all of my documentation to update version numbers upon each release. It's useful both in templates (it's conceivable that I'd like to have the version number of my library a few places in my template) and markdown documentation files.

So I attest that custom variables that templates + users can use **are** super useful. Jekyll is excellent validation of this.  

I've lead the documentation of the entire Scala programming language using Jekyll (we were looking to mkdocs because it'd allow us to include docs in our build a bit easier than Jekyll), and I promise you that having the ability to define custom, globally accessible variables in config.yml **is** useful for real, big project documentation.

> It's useful both in templates (it's conceivable that I'd like to have the version number of my library a few places in my template) and markdown documentation files.

Right, but the previous implementation of this only supported it in the template, and _not_ the Markdown files, which makes it a lot less useful (unless you want to write your own custom theme, which I think is overkill for showing a version number in the appropriate spots; and even then, it wouldn't be usable in the body of the docs).

To be clear, I'm not saying your suggestion is invalid; it's totally valid. I'm just saying that the old implementation of custom globals in MkDocs wasn't very useful.

Just to make sure this point isn't lost in the quick reply.

>  It's useful both in templates (it's conceivable that I'd like to have the version number of my library a few places in my template)

I think my statement right there is validation that it is useful. There's more than a version number that I'd maybe like to define globally. For example, a list of contributor names, project title, tagline, an email address, social media URLs. 

So to reiterate once again – I think it is useful in the template as well.

And PS, I've already written my own theme. I still can't use any custom globally defined variable in it.

Well, if you _just_ want global variables for a theme without forwarding to the Markdown files, then you want any commit before 7167406bc9410410ea7617cd3ed30848636c45d8. It should be pretty straightforward to undo the relevant part of that commit on master (you'd just need to undo [this line](https://github.com/tomchristie/mkdocs/commit/7167406bc9410410ea7617cd3ed30848636c45d8#diff-96741725c22c18143d716fb4f55a07faL113)), but for it to be more generally useful, I think you'd want to forward the variable along to the Markdown files.

Really, I think part of the issue is that the set of predefined global variables is rather sparse to begin with...

Also, I suppose it should be noted that I'm mainly just a user of MkDocs, and as such I don't have any real say in anything here, but I think your suggestion would be pretty useful, provided the variables could be referenced in the Markdown files as well as the templates.

I think given @tomchristie removed this ability recently we probably need to get some input from him on this. Otherwise the use case does seem reasonable to me.

+1 for global template variables

Okay, so issue here is that if we simply pass the _entire_ config through to the context then it becomes unclear what variables are user-defined, and what are built-in.

Options:
- Not worry about that, just do it anyway. Probably pass the config in as `config` so that it is namespaced.
- Use `custom_<my_variable>` names or nested `custom:` to pass through custom variables.

This...

```
custom:
    my_custom_variable: "Blah"
    another_one: 123
```

Passed through to the templates as `custom` seems like it might be a good option?

+1 to the last option, a key/value mapping of custom data. That sounds like a good approach to me but I'm not sure about the name `custom` - it doesn't strike me as self explanitory. The one I'd use naturally is `context` but that is due to a Jinja2/Django background so I don't think it makes sense here.

Something like `template-variables` might make sense, but it gets quite verbose.

As @heathermiller mentioned Jekyll has a similar feature, I had a look to see how they do it - http://jekyllrb.com/docs/datafiles/

Probably overkill for us, but maybe worth considering.

Possibly this...
- Name it `custom` or `extra` or `ext` or `user_defined` or whatever. You can put whatever you want under there.
- Pass the whole config to the templates as `config`.
- Error or warn if there are ever config variables that are unknown.

Reading through this PR, it occurs to me that the original feature request assumes that the user can define any custom settings (including settings that mkdocs knows nothing about) and that those should then be available in the template. However, the work in #496 makes that impossible as only "known" settings will validate. Creating an "unknown" setting would result in an error. In that case, a custom setting which contains a dict of user defined keys and values makes sense. Something like `custom`, `extra`, or `user_defined` seems logical (I like `extra`). Then, that setting can be passed to the template regardless of whether all of the config is.

I suppose the real question is: Is this issue about the custom user-defined settings or all settings being available in the template. If it is the former, then that should be an easy addition to #496 and the rest can be punted for later consideration. However, if it is the later, a final decision needs to be made first.

For the record,  I don't understand the aversion to having all of the config available to the templates. For Markdown files, I agree that not everything should be available (if #304 ever gets implemented), but whats the big deal for templates?

@waylan 

> For the record,  I don't understand the aversion to having all of the config available to the templates. For Markdown files, I agree that not everything should be available (if #304 ever gets implemented), but whats the big deal for templates?

I'm not sure anyone is against it really, just trying to make the most sensible choice. I think it would make sense to pass the full config.

I'd like to look at this after I finish the work in #496. I think my plan will be to make the full config available as `{{ config.setting_name }}` to templates. Then use `extra` to have a dictionary of whatever the user wants. Then it could be accessed with `{{ config.extra.my_custom_thing }}`.

Over time I'd like to remove the [duplication with settings](https://github.com/mkdocs/mkdocs/pull/503/files#diff-cbfacbafa3042a0b35cee6270e6ca67aR130) in the template context, and have them all accessible via `{{ config }}` - that would probably be a switch for a `1.0` release.


Resolving patch:
diff --git a/mkdocs/build.py b/mkdocs/build.py
--- a/mkdocs/build.py
+++ b/mkdocs/build.py
@@ -91,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']
     }
 
 
diff --git a/mkdocs/config/defaults.py b/mkdocs/config/defaults.py
--- a/mkdocs/config/defaults.py
+++ b/mkdocs/config/defaults.py
@@ -96,4 +96,11 @@
     # 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={})),
 )


Test patch:
diff --git a/mkdocs/tests/build_tests.py b/mkdocs/tests/build_tests.py
--- a/mkdocs/tests/build_tests.py
+++ b/mkdocs/tests/build_tests.py
@@ -7,6 +7,7 @@
 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 @@ def test_extension_config(self):
         """)
 
         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
+        })