We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Eliminate duplicate warnings
A few recent updates have added warnings for deprecated behavior (#921 & #947). However, the same warnings are issued once for every single page. The more pages in a project, the more warnings are issued. Each warning only needs to be printed to the console once per build. 

[This](http://stackoverflow.com/a/31953563/866026) may be a potential solution. Will explore when I have some time unless someone beats me to it.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mkdocs/commands/build.py`
Content:
```
1 # coding: utf-8
2 
3 from __future__ import unicode_literals
4 from datetime import datetime
5 from calendar import timegm
6 import io
7 import logging
8 import os
9 
10 from jinja2.exceptions import TemplateNotFound
11 import jinja2
12 import json
13 
14 from mkdocs import nav, search, utils
15 from mkdocs.utils import filters
16 from mkdocs.relative_path_ext import RelativePathExtension
17 import mkdocs
18 
19 log = logging.getLogger(__name__)
20 
21 
22 def convert_markdown(markdown_source, config, site_navigation=None):
23     """
24     Convert the Markdown source file to HTML as per the config and
25     site_navigation. Return a tuple of the HTML as a string, the parsed table
26     of contents, and a dictionary of any metadata that was specified in the
27     Markdown file.
28     """
29 
30     extensions = [
31         RelativePathExtension(site_navigation, config['strict'])
32     ] + config['markdown_extensions']
33 
34     return utils.convert_markdown(
35         markdown_source=markdown_source,
36         extensions=extensions,
37         extension_configs=config['mdx_configs']
38     )
39 
40 
41 def get_global_context(nav, config):
42     """
43     Given the SiteNavigation and config, generate the context which is relevant
44     to app pages.
45     """
46 
47     site_name = config['site_name']
48 
49     if config['site_favicon']:
50         site_favicon = nav.url_context.make_relative('/' + config['site_favicon'])
51     else:
52         site_favicon = None
53 
54     page_description = config['site_description']
55 
56     extra_javascript = utils.create_media_urls(nav, config['extra_javascript'])
57 
58     extra_css = utils.create_media_urls(nav, config['extra_css'])
59 
60     # Support SOURCE_DATE_EPOCH environment variable for "reproducible" builds.
61     # See https://reproducible-builds.org/specs/source-date-epoch/
62     timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))
63 
64     return {
65         'site_name': site_name,
66         'site_author': config['site_author'],
67         'favicon': site_favicon,
68         'page_description': page_description,
69 
70         # Note that there's intentionally repetition here. Rather than simply
71         # provide the config dictionary we instead pass everything explicitly.
72         #
73         # This helps ensure that we can throughly document the context that
74         # gets passed to themes.
75         'repo_url': config['repo_url'],
76         'repo_name': config['repo_name'],
77         'nav': nav,
78         'base_url': nav.url_context.make_relative('/'),
79         'homepage_url': nav.homepage.url,
80         'site_url': config['site_url'],
81 
82         'extra_css': extra_css,
83         'extra_javascript': extra_javascript,
84 
85         'include_nav': config['include_nav'],
86         'include_next_prev': config['include_next_prev'],
87 
88         'copyright': config['copyright'],
89         'google_analytics': config['google_analytics'],
90 
91         'mkdocs_version': mkdocs.__version__,
92         'build_date_utc': datetime.utcfromtimestamp(timestamp),
93 
94         'config': config
95     }
96 
97 
98 def get_page_context(page, content, toc, meta, config):
99     """
100     Generate the page context by extending the global context and adding page
101     specific variables.
102     """
103     if config['site_url']:
104         page.set_canonical_url(config['site_url'])
105 
106     page.content = content
107     page.toc = toc
108     page.meta = meta
109 
110     # TODO: remove the rest in version 1.0 as they are deprecated
111 
112     if page.is_homepage or page.title is None:
113         page_title = None
114     else:
115         page_title = page.title
116 
117     if page.is_homepage:
118         page_description = config['site_description']
119     else:
120         page_description = None
121 
122     return {
123         'page': page,
124         # TODO: remove the rest in version 1.0 as they are deprecated
125         'page_title': page_title,
126         'page_description': page_description,
127 
128         'content': content,
129         'toc': toc,
130         'meta': meta,
131 
132         'canonical_url': page.canonical_url,
133 
134         'current_page': page,
135         'previous_page': page.previous_page,
136         'next_page': page.next_page
137     }
138 
139 
140 def build_template(template_name, env, config, site_navigation=None):
141 
142     log.debug("Building template: %s", template_name)
143 
144     try:
145         template = env.get_template(template_name)
146     except TemplateNotFound:
147         return False
148 
149     context = {'page': None}
150     if site_navigation is not None:
151         context.update(get_global_context(site_navigation, config))
152 
153     output_content = template.render(context)
154     output_path = os.path.join(config['site_dir'], template_name)
155     utils.write_file(output_content.encode('utf-8'), output_path)
156     return True
157 
158 
159 def _build_page(page, config, site_navigation, env, dump_json):
160 
161     # Read the input file
162     input_path = os.path.join(config['docs_dir'], page.input_path)
163 
164     try:
165         input_content = io.open(input_path, 'r', encoding='utf-8').read()
166     except IOError:
167         log.error('file not found: %s', input_path)
168         raise
169 
170     # Process the markdown text
171     html_content, table_of_contents, meta = convert_markdown(
172         markdown_source=input_content,
173         config=config,
174         site_navigation=site_navigation
175     )
176 
177     context = get_global_context(site_navigation, config)
178     context.update(get_page_context(
179         page, html_content, table_of_contents, meta, config
180     ))
181 
182     # Allow 'template:' override in md source files.
183     if 'template' in meta:
184         template = env.get_template(meta['template'][0])
185     else:
186         try:
187             template = env.get_template('main.html')
188         except jinja2.TemplateNotFound:
189             # TODO: Remove this in version 1.0
190             template = env.get_template('base.html')
191             log.warn(
192                 "Your theme does not appear to contain a 'main.html' template. "
193                 "The 'base.html' template was used instead, which is deprecated. "
194                 "Update your theme so that the primary entry point is 'main.html'."
195             )
196 
197     # Render the template.
198     output_content = template.render(context)
199 
200     # Write the output file.
201     output_path = os.path.join(config['site_dir'], page.output_path)
202     if dump_json:
203         json_context = {
204             'content': context['content'],
205             'title': context['current_page'].title,
206             'url': context['current_page'].abs_url,
207             'language': 'en',
208         }
209         json_output = json.dumps(json_context, indent=4).encode('utf-8')
210         utils.write_file(json_output, output_path.replace('.html', '.json'))
211     else:
212         utils.write_file(output_content.encode('utf-8'), output_path)
213 
214     return html_content, table_of_contents, meta
215 
216 
217 def build_extra_templates(extra_templates, config, site_navigation=None):
218 
219     log.debug("Building extra_templates page")
220 
221     for extra_template in extra_templates:
222 
223         input_path = os.path.join(config['docs_dir'], extra_template)
224 
225         with io.open(input_path, 'r', encoding='utf-8') as template_file:
226             template = jinja2.Template(template_file.read())
227 
228         context = {'page': None}
229         if site_navigation is not None:
230             context.update(get_global_context(site_navigation, config))
231 
232         output_content = template.render(context)
233         output_path = os.path.join(config['site_dir'], extra_template)
234         utils.write_file(output_content.encode('utf-8'), output_path)
235 
236 
237 def build_pages(config, dump_json=False):
238     """
239     Builds all the pages and writes them into the build directory.
240     """
241     site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])
242     loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])
243     env = jinja2.Environment(loader=loader)
244 
245     # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed
246     from jinja2.runtime import Context
247     deprecated_vars = [
248         'page_title',
249         'content',
250         'toc',
251         'meta',
252         'current_page',
253         'canonical_url',
254         'previous_page',
255         'next_page'
256     ]
257 
258     class DeprecationContext(Context):
259         def resolve(self, key):
260             """ Log a warning when acessing any deprecated variable name. """
261             if key in deprecated_vars:
262                 replacement = "page" if key == 'current_page' else "page.{0}".format(key)
263                 log.warn(
264                     "Template variable warning: '{0}' is being deprecated and will not be "
265                     "available in a future version. Use '{1}' instead.".format(key, replacement)
266                 )
267             return super(DeprecationContext, self).resolve(key)
268 
269     env.context_class = DeprecationContext
270     # TODO: end remove DeprecationContext
271 
272     env.filters['tojson'] = filters.tojson
273     search_index = search.SearchIndex()
274 
275     build_template('404.html', env, config, site_navigation)
276 
277     if not build_template('search.html', env, config, site_navigation):
278         log.debug("Search is enabled but the theme doesn't contain a "
279                   "search.html file. Assuming the theme implements search "
280                   "within a modal.")
281 
282     build_template('sitemap.xml', env, config, site_navigation)
283 
284     build_extra_templates(config['extra_templates'], config, site_navigation)
285 
286     for page in site_navigation.walk_pages():
287 
288         try:
289             log.debug("Building page %s", page.input_path)
290             build_result = _build_page(page, config, site_navigation, env,
291                                        dump_json)
292             html_content, table_of_contents, _ = build_result
293             search_index.add_entry_from_context(
294                 page, html_content, table_of_contents)
295         except Exception:
296             log.error("Error building page %s", page.input_path)
297             raise
298 
299     search_index = search_index.generate_search_index()
300     json_output_path = os.path.join(config['site_dir'], 'mkdocs', 'search_index.json')
301     utils.write_file(search_index.encode('utf-8'), json_output_path)
302 
303 
304 def build(config, live_server=False, dump_json=False, clean_site_dir=False):
305     """
306     Perform a full site build.
307     """
308     if clean_site_dir:
309         log.info("Cleaning site directory")
310         utils.clean_directory(config['site_dir'])
311     if not live_server:
312         log.info("Building documentation to directory: %s", config['site_dir'])
313         if not clean_site_dir and site_directory_contains_stale_files(config['site_dir']):
314             log.info("The directory contains stale files. Use --clean to remove them.")
315 
316     if dump_json:
317         build_pages(config, dump_json=True)
318         return
319 
320     # Reversed as we want to take the media files from the builtin theme
321     # and then from the custom theme_dir so that the custom versions take
322     # precedence.
323     for theme_dir in reversed(config['theme_dir']):
324         log.debug("Copying static assets from theme: %s", theme_dir)
325         utils.copy_media_files(
326             theme_dir, config['site_dir'], exclude=['*.py', '*.pyc', '*.html']
327         )
328 
329     log.debug("Copying static assets from the docs dir.")
330     utils.copy_media_files(config['docs_dir'], config['site_dir'])
331 
332     log.debug("Building markdown pages.")
333     build_pages(config)
334 
335 
336 def site_directory_contains_stale_files(site_directory):
337     """
338     Check if the site directory contains stale files from a previous build.
339     Right now the check returns true if the directory is not empty.
340     A more sophisticated approach should be found to trigger only if there are
341     files that won't be overwritten anyway.
342     """
343     if os.path.exists(site_directory):
344         if os.listdir(site_directory):
345             return True
346     return False
347
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py
--- a/mkdocs/commands/build.py
+++ b/mkdocs/commands/build.py
@@ -16,7 +16,20 @@
 from mkdocs.relative_path_ext import RelativePathExtension
 import mkdocs
 
+
+class DuplicateFilter(object):
+    ''' Avoid logging duplicate messages. '''
+    def __init__(self):
+        self.msgs = set()
+
+    def filter(self, record):
+        rv = record.msg not in self.msgs
+        self.msgs.add(record.msg)
+        return rv
+
+
 log = logging.getLogger(__name__)
+log.addFilter(DuplicateFilter())
 
 
 def convert_markdown(markdown_source, config, site_navigation=None):

{"golden_diff": "diff --git a/mkdocs/commands/build.py b/mkdocs/commands/build.py\n--- a/mkdocs/commands/build.py\n+++ b/mkdocs/commands/build.py\n@@ -16,7 +16,20 @@\n from mkdocs.relative_path_ext import RelativePathExtension\n import mkdocs\n \n+\n+class DuplicateFilter(object):\n+    ''' Avoid logging duplicate messages. '''\n+    def __init__(self):\n+        self.msgs = set()\n+\n+    def filter(self, record):\n+        rv = record.msg not in self.msgs\n+        self.msgs.add(record.msg)\n+        return rv\n+\n+\n log = logging.getLogger(__name__)\n+log.addFilter(DuplicateFilter())\n \n \n def convert_markdown(markdown_source, config, site_navigation=None):\n", "issue": "Eliminate duplicate warnings\nA few recent updates have added warnings for deprecated behavior (#921 & #947). However, the same warnings are issued once for every single page. The more pages in a project, the more warnings are issued. Each warning only needs to be printed to the console once per build. \n\n[This](http://stackoverflow.com/a/31953563/866026) may be a potential solution. Will explore when I have some time unless someone beats me to it.\n\n", "before_files": [{"content": "# coding: utf-8\n\nfrom __future__ import unicode_literals\nfrom datetime import datetime\nfrom calendar import timegm\nimport io\nimport logging\nimport os\n\nfrom jinja2.exceptions import TemplateNotFound\nimport jinja2\nimport json\n\nfrom mkdocs import nav, search, utils\nfrom mkdocs.utils import filters\nfrom mkdocs.relative_path_ext import RelativePathExtension\nimport mkdocs\n\nlog = logging.getLogger(__name__)\n\n\ndef convert_markdown(markdown_source, config, site_navigation=None):\n    \"\"\"\n    Convert the Markdown source file to HTML as per the config and\n    site_navigation. Return a tuple of the HTML as a string, the parsed table\n    of contents, and a dictionary of any metadata that was specified in the\n    Markdown file.\n    \"\"\"\n\n    extensions = [\n        RelativePathExtension(site_navigation, config['strict'])\n    ] + config['markdown_extensions']\n\n    return utils.convert_markdown(\n        markdown_source=markdown_source,\n        extensions=extensions,\n        extension_configs=config['mdx_configs']\n    )\n\n\ndef get_global_context(nav, config):\n    \"\"\"\n    Given the SiteNavigation and config, generate the context which is relevant\n    to app pages.\n    \"\"\"\n\n    site_name = config['site_name']\n\n    if config['site_favicon']:\n        site_favicon = nav.url_context.make_relative('/' + config['site_favicon'])\n    else:\n        site_favicon = None\n\n    page_description = config['site_description']\n\n    extra_javascript = utils.create_media_urls(nav, config['extra_javascript'])\n\n    extra_css = utils.create_media_urls(nav, config['extra_css'])\n\n    # Support SOURCE_DATE_EPOCH environment variable for \"reproducible\" builds.\n    # See https://reproducible-builds.org/specs/source-date-epoch/\n    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))\n\n    return {\n        'site_name': site_name,\n        'site_author': config['site_author'],\n        'favicon': site_favicon,\n        'page_description': page_description,\n\n        # Note that there's intentionally repetition here. Rather than simply\n        # provide the config dictionary we instead pass everything explicitly.\n        #\n        # This helps ensure that we can throughly document the context that\n        # gets passed to themes.\n        'repo_url': config['repo_url'],\n        'repo_name': config['repo_name'],\n        'nav': nav,\n        'base_url': nav.url_context.make_relative('/'),\n        'homepage_url': nav.homepage.url,\n        'site_url': config['site_url'],\n\n        'extra_css': extra_css,\n        'extra_javascript': extra_javascript,\n\n        'include_nav': config['include_nav'],\n        'include_next_prev': config['include_next_prev'],\n\n        'copyright': config['copyright'],\n        'google_analytics': config['google_analytics'],\n\n        'mkdocs_version': mkdocs.__version__,\n        'build_date_utc': datetime.utcfromtimestamp(timestamp),\n\n        'config': config\n    }\n\n\ndef get_page_context(page, content, toc, meta, config):\n    \"\"\"\n    Generate the page context by extending the global context and adding page\n    specific variables.\n    \"\"\"\n    if config['site_url']:\n        page.set_canonical_url(config['site_url'])\n\n    page.content = content\n    page.toc = toc\n    page.meta = meta\n\n    # TODO: remove the rest in version 1.0 as they are deprecated\n\n    if page.is_homepage or page.title is None:\n        page_title = None\n    else:\n        page_title = page.title\n\n    if page.is_homepage:\n        page_description = config['site_description']\n    else:\n        page_description = None\n\n    return {\n        'page': page,\n        # TODO: remove the rest in version 1.0 as they are deprecated\n        'page_title': page_title,\n        'page_description': page_description,\n\n        'content': content,\n        'toc': toc,\n        'meta': meta,\n\n        'canonical_url': page.canonical_url,\n\n        'current_page': page,\n        'previous_page': page.previous_page,\n        'next_page': page.next_page\n    }\n\n\ndef build_template(template_name, env, config, site_navigation=None):\n\n    log.debug(\"Building template: %s\", template_name)\n\n    try:\n        template = env.get_template(template_name)\n    except TemplateNotFound:\n        return False\n\n    context = {'page': None}\n    if site_navigation is not None:\n        context.update(get_global_context(site_navigation, config))\n\n    output_content = template.render(context)\n    output_path = os.path.join(config['site_dir'], template_name)\n    utils.write_file(output_content.encode('utf-8'), output_path)\n    return True\n\n\ndef _build_page(page, config, site_navigation, env, dump_json):\n\n    # Read the input file\n    input_path = os.path.join(config['docs_dir'], page.input_path)\n\n    try:\n        input_content = io.open(input_path, 'r', encoding='utf-8').read()\n    except IOError:\n        log.error('file not found: %s', input_path)\n        raise\n\n    # Process the markdown text\n    html_content, table_of_contents, meta = convert_markdown(\n        markdown_source=input_content,\n        config=config,\n        site_navigation=site_navigation\n    )\n\n    context = get_global_context(site_navigation, config)\n    context.update(get_page_context(\n        page, html_content, table_of_contents, meta, config\n    ))\n\n    # Allow 'template:' override in md source files.\n    if 'template' in meta:\n        template = env.get_template(meta['template'][0])\n    else:\n        try:\n            template = env.get_template('main.html')\n        except jinja2.TemplateNotFound:\n            # TODO: Remove this in version 1.0\n            template = env.get_template('base.html')\n            log.warn(\n                \"Your theme does not appear to contain a 'main.html' template. \"\n                \"The 'base.html' template was used instead, which is deprecated. \"\n                \"Update your theme so that the primary entry point is 'main.html'.\"\n            )\n\n    # Render the template.\n    output_content = template.render(context)\n\n    # Write the output file.\n    output_path = os.path.join(config['site_dir'], page.output_path)\n    if dump_json:\n        json_context = {\n            'content': context['content'],\n            'title': context['current_page'].title,\n            'url': context['current_page'].abs_url,\n            'language': 'en',\n        }\n        json_output = json.dumps(json_context, indent=4).encode('utf-8')\n        utils.write_file(json_output, output_path.replace('.html', '.json'))\n    else:\n        utils.write_file(output_content.encode('utf-8'), output_path)\n\n    return html_content, table_of_contents, meta\n\n\ndef build_extra_templates(extra_templates, config, site_navigation=None):\n\n    log.debug(\"Building extra_templates page\")\n\n    for extra_template in extra_templates:\n\n        input_path = os.path.join(config['docs_dir'], extra_template)\n\n        with io.open(input_path, 'r', encoding='utf-8') as template_file:\n            template = jinja2.Template(template_file.read())\n\n        context = {'page': None}\n        if site_navigation is not None:\n            context.update(get_global_context(site_navigation, config))\n\n        output_content = template.render(context)\n        output_path = os.path.join(config['site_dir'], extra_template)\n        utils.write_file(output_content.encode('utf-8'), output_path)\n\n\ndef build_pages(config, dump_json=False):\n    \"\"\"\n    Builds all the pages and writes them into the build directory.\n    \"\"\"\n    site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])\n    loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])\n    env = jinja2.Environment(loader=loader)\n\n    # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed\n    from jinja2.runtime import Context\n    deprecated_vars = [\n        'page_title',\n        'content',\n        'toc',\n        'meta',\n        'current_page',\n        'canonical_url',\n        'previous_page',\n        'next_page'\n    ]\n\n    class DeprecationContext(Context):\n        def resolve(self, key):\n            \"\"\" Log a warning when acessing any deprecated variable name. \"\"\"\n            if key in deprecated_vars:\n                replacement = \"page\" if key == 'current_page' else \"page.{0}\".format(key)\n                log.warn(\n                    \"Template variable warning: '{0}' is being deprecated and will not be \"\n                    \"available in a future version. Use '{1}' instead.\".format(key, replacement)\n                )\n            return super(DeprecationContext, self).resolve(key)\n\n    env.context_class = DeprecationContext\n    # TODO: end remove DeprecationContext\n\n    env.filters['tojson'] = filters.tojson\n    search_index = search.SearchIndex()\n\n    build_template('404.html', env, config, site_navigation)\n\n    if not build_template('search.html', env, config, site_navigation):\n        log.debug(\"Search is enabled but the theme doesn't contain a \"\n                  \"search.html file. Assuming the theme implements search \"\n                  \"within a modal.\")\n\n    build_template('sitemap.xml', env, config, site_navigation)\n\n    build_extra_templates(config['extra_templates'], config, site_navigation)\n\n    for page in site_navigation.walk_pages():\n\n        try:\n            log.debug(\"Building page %s\", page.input_path)\n            build_result = _build_page(page, config, site_navigation, env,\n                                       dump_json)\n            html_content, table_of_contents, _ = build_result\n            search_index.add_entry_from_context(\n                page, html_content, table_of_contents)\n        except Exception:\n            log.error(\"Error building page %s\", page.input_path)\n            raise\n\n    search_index = search_index.generate_search_index()\n    json_output_path = os.path.join(config['site_dir'], 'mkdocs', 'search_index.json')\n    utils.write_file(search_index.encode('utf-8'), json_output_path)\n\n\ndef build(config, live_server=False, dump_json=False, clean_site_dir=False):\n    \"\"\"\n    Perform a full site build.\n    \"\"\"\n    if clean_site_dir:\n        log.info(\"Cleaning site directory\")\n        utils.clean_directory(config['site_dir'])\n    if not live_server:\n        log.info(\"Building documentation to directory: %s\", config['site_dir'])\n        if not clean_site_dir and site_directory_contains_stale_files(config['site_dir']):\n            log.info(\"The directory contains stale files. Use --clean to remove them.\")\n\n    if dump_json:\n        build_pages(config, dump_json=True)\n        return\n\n    # Reversed as we want to take the media files from the builtin theme\n    # and then from the custom theme_dir so that the custom versions take\n    # precedence.\n    for theme_dir in reversed(config['theme_dir']):\n        log.debug(\"Copying static assets from theme: %s\", theme_dir)\n        utils.copy_media_files(\n            theme_dir, config['site_dir'], exclude=['*.py', '*.pyc', '*.html']\n        )\n\n    log.debug(\"Copying static assets from the docs dir.\")\n    utils.copy_media_files(config['docs_dir'], config['site_dir'])\n\n    log.debug(\"Building markdown pages.\")\n    build_pages(config)\n\n\ndef site_directory_contains_stale_files(site_directory):\n    \"\"\"\n    Check if the site directory contains stale files from a previous build.\n    Right now the check returns true if the directory is not empty.\n    A more sophisticated approach should be found to trigger only if there are\n    files that won't be overwritten anyway.\n    \"\"\"\n    if os.path.exists(site_directory):\n        if os.listdir(site_directory):\n            return True\n    return False\n", "path": "mkdocs/commands/build.py"}], "after_files": [{"content": "# coding: utf-8\n\nfrom __future__ import unicode_literals\nfrom datetime import datetime\nfrom calendar import timegm\nimport io\nimport logging\nimport os\n\nfrom jinja2.exceptions import TemplateNotFound\nimport jinja2\nimport json\n\nfrom mkdocs import nav, search, utils\nfrom mkdocs.utils import filters\nfrom mkdocs.relative_path_ext import RelativePathExtension\nimport mkdocs\n\n\nclass DuplicateFilter(object):\n    ''' Avoid logging duplicate messages. '''\n    def __init__(self):\n        self.msgs = set()\n\n    def filter(self, record):\n        rv = record.msg not in self.msgs\n        self.msgs.add(record.msg)\n        return rv\n\n\nlog = logging.getLogger(__name__)\nlog.addFilter(DuplicateFilter())\n\n\ndef convert_markdown(markdown_source, config, site_navigation=None):\n    \"\"\"\n    Convert the Markdown source file to HTML as per the config and\n    site_navigation. Return a tuple of the HTML as a string, the parsed table\n    of contents, and a dictionary of any metadata that was specified in the\n    Markdown file.\n    \"\"\"\n\n    extensions = [\n        RelativePathExtension(site_navigation, config['strict'])\n    ] + config['markdown_extensions']\n\n    return utils.convert_markdown(\n        markdown_source=markdown_source,\n        extensions=extensions,\n        extension_configs=config['mdx_configs']\n    )\n\n\ndef get_global_context(nav, config):\n    \"\"\"\n    Given the SiteNavigation and config, generate the context which is relevant\n    to app pages.\n    \"\"\"\n\n    site_name = config['site_name']\n\n    if config['site_favicon']:\n        site_favicon = nav.url_context.make_relative('/' + config['site_favicon'])\n    else:\n        site_favicon = None\n\n    page_description = config['site_description']\n\n    extra_javascript = utils.create_media_urls(nav, config['extra_javascript'])\n\n    extra_css = utils.create_media_urls(nav, config['extra_css'])\n\n    # Support SOURCE_DATE_EPOCH environment variable for \"reproducible\" builds.\n    # See https://reproducible-builds.org/specs/source-date-epoch/\n    timestamp = int(os.environ.get('SOURCE_DATE_EPOCH', timegm(datetime.utcnow().utctimetuple())))\n\n    return {\n        'site_name': site_name,\n        'site_author': config['site_author'],\n        'favicon': site_favicon,\n        'page_description': page_description,\n\n        # Note that there's intentionally repetition here. Rather than simply\n        # provide the config dictionary we instead pass everything explicitly.\n        #\n        # This helps ensure that we can throughly document the context that\n        # gets passed to themes.\n        'repo_url': config['repo_url'],\n        'repo_name': config['repo_name'],\n        'nav': nav,\n        'base_url': nav.url_context.make_relative('/'),\n        'homepage_url': nav.homepage.url,\n        'site_url': config['site_url'],\n\n        'extra_css': extra_css,\n        'extra_javascript': extra_javascript,\n\n        'include_nav': config['include_nav'],\n        'include_next_prev': config['include_next_prev'],\n\n        'copyright': config['copyright'],\n        'google_analytics': config['google_analytics'],\n\n        'mkdocs_version': mkdocs.__version__,\n        'build_date_utc': datetime.utcfromtimestamp(timestamp),\n\n        'config': config\n    }\n\n\ndef get_page_context(page, content, toc, meta, config):\n    \"\"\"\n    Generate the page context by extending the global context and adding page\n    specific variables.\n    \"\"\"\n    if config['site_url']:\n        page.set_canonical_url(config['site_url'])\n\n    page.content = content\n    page.toc = toc\n    page.meta = meta\n\n    # TODO: remove the rest in version 1.0 as they are deprecated\n\n    if page.is_homepage or page.title is None:\n        page_title = None\n    else:\n        page_title = page.title\n\n    if page.is_homepage:\n        page_description = config['site_description']\n    else:\n        page_description = None\n\n    return {\n        'page': page,\n        # TODO: remove the rest in version 1.0 as they are deprecated\n        'page_title': page_title,\n        'page_description': page_description,\n\n        'content': content,\n        'toc': toc,\n        'meta': meta,\n\n        'canonical_url': page.canonical_url,\n\n        'current_page': page,\n        'previous_page': page.previous_page,\n        'next_page': page.next_page\n    }\n\n\ndef build_template(template_name, env, config, site_navigation=None):\n\n    log.debug(\"Building template: %s\", template_name)\n\n    try:\n        template = env.get_template(template_name)\n    except TemplateNotFound:\n        return False\n\n    context = {'page': None}\n    if site_navigation is not None:\n        context.update(get_global_context(site_navigation, config))\n\n    output_content = template.render(context)\n    output_path = os.path.join(config['site_dir'], template_name)\n    utils.write_file(output_content.encode('utf-8'), output_path)\n    return True\n\n\ndef _build_page(page, config, site_navigation, env, dump_json):\n\n    # Read the input file\n    input_path = os.path.join(config['docs_dir'], page.input_path)\n\n    try:\n        input_content = io.open(input_path, 'r', encoding='utf-8').read()\n    except IOError:\n        log.error('file not found: %s', input_path)\n        raise\n\n    # Process the markdown text\n    html_content, table_of_contents, meta = convert_markdown(\n        markdown_source=input_content,\n        config=config,\n        site_navigation=site_navigation\n    )\n\n    context = get_global_context(site_navigation, config)\n    context.update(get_page_context(\n        page, html_content, table_of_contents, meta, config\n    ))\n\n    # Allow 'template:' override in md source files.\n    if 'template' in meta:\n        template = env.get_template(meta['template'][0])\n    else:\n        try:\n            template = env.get_template('main.html')\n        except jinja2.TemplateNotFound:\n            # TODO: Remove this in version 1.0\n            template = env.get_template('base.html')\n            log.warn(\n                \"Your theme does not appear to contain a 'main.html' template. \"\n                \"The 'base.html' template was used instead, which is deprecated. \"\n                \"Update your theme so that the primary entry point is 'main.html'.\"\n            )\n\n    # Render the template.\n    output_content = template.render(context)\n\n    # Write the output file.\n    output_path = os.path.join(config['site_dir'], page.output_path)\n    if dump_json:\n        json_context = {\n            'content': context['content'],\n            'title': context['current_page'].title,\n            'url': context['current_page'].abs_url,\n            'language': 'en',\n        }\n        json_output = json.dumps(json_context, indent=4).encode('utf-8')\n        utils.write_file(json_output, output_path.replace('.html', '.json'))\n    else:\n        utils.write_file(output_content.encode('utf-8'), output_path)\n\n    return html_content, table_of_contents, meta\n\n\ndef build_extra_templates(extra_templates, config, site_navigation=None):\n\n    log.debug(\"Building extra_templates page\")\n\n    for extra_template in extra_templates:\n\n        input_path = os.path.join(config['docs_dir'], extra_template)\n\n        with io.open(input_path, 'r', encoding='utf-8') as template_file:\n            template = jinja2.Template(template_file.read())\n\n        context = {'page': None}\n        if site_navigation is not None:\n            context.update(get_global_context(site_navigation, config))\n\n        output_content = template.render(context)\n        output_path = os.path.join(config['site_dir'], extra_template)\n        utils.write_file(output_content.encode('utf-8'), output_path)\n\n\ndef build_pages(config, dump_json=False):\n    \"\"\"\n    Builds all the pages and writes them into the build directory.\n    \"\"\"\n    site_navigation = nav.SiteNavigation(config['pages'], config['use_directory_urls'])\n    loader = jinja2.FileSystemLoader(config['theme_dir'] + [config['mkdocs_templates'], ])\n    env = jinja2.Environment(loader=loader)\n\n    # TODO: remove DeprecationContext in v1.0 when all deprecated vars have been removed\n    from jinja2.runtime import Context\n    deprecated_vars = [\n        'page_title',\n        'content',\n        'toc',\n        'meta',\n        'current_page',\n        'canonical_url',\n        'previous_page',\n        'next_page'\n    ]\n\n    class DeprecationContext(Context):\n        def resolve(self, key):\n            \"\"\" Log a warning when acessing any deprecated variable name. \"\"\"\n            if key in deprecated_vars:\n                replacement = \"page\" if key == 'current_page' else \"page.{0}\".format(key)\n                log.warn(\n                    \"Template variable warning: '{0}' is being deprecated and will not be \"\n                    \"available in a future version. Use '{1}' instead.\".format(key, replacement)\n                )\n            return super(DeprecationContext, self).resolve(key)\n\n    env.context_class = DeprecationContext\n    # TODO: end remove DeprecationContext\n\n    env.filters['tojson'] = filters.tojson\n    search_index = search.SearchIndex()\n\n    build_template('404.html', env, config, site_navigation)\n\n    if not build_template('search.html', env, config, site_navigation):\n        log.debug(\"Search is enabled but the theme doesn't contain a \"\n                  \"search.html file. Assuming the theme implements search \"\n                  \"within a modal.\")\n\n    build_template('sitemap.xml', env, config, site_navigation)\n\n    build_extra_templates(config['extra_templates'], config, site_navigation)\n\n    for page in site_navigation.walk_pages():\n\n        try:\n            log.debug(\"Building page %s\", page.input_path)\n            build_result = _build_page(page, config, site_navigation, env,\n                                       dump_json)\n            html_content, table_of_contents, _ = build_result\n            search_index.add_entry_from_context(\n                page, html_content, table_of_contents)\n        except Exception:\n            log.error(\"Error building page %s\", page.input_path)\n            raise\n\n    search_index = search_index.generate_search_index()\n    json_output_path = os.path.join(config['site_dir'], 'mkdocs', 'search_index.json')\n    utils.write_file(search_index.encode('utf-8'), json_output_path)\n\n\ndef build(config, live_server=False, dump_json=False, clean_site_dir=False):\n    \"\"\"\n    Perform a full site build.\n    \"\"\"\n    if clean_site_dir:\n        log.info(\"Cleaning site directory\")\n        utils.clean_directory(config['site_dir'])\n    if not live_server:\n        log.info(\"Building documentation to directory: %s\", config['site_dir'])\n        if not clean_site_dir and site_directory_contains_stale_files(config['site_dir']):\n            log.info(\"The directory contains stale files. Use --clean to remove them.\")\n\n    if dump_json:\n        build_pages(config, dump_json=True)\n        return\n\n    # Reversed as we want to take the media files from the builtin theme\n    # and then from the custom theme_dir so that the custom versions take\n    # precedence.\n    for theme_dir in reversed(config['theme_dir']):\n        log.debug(\"Copying static assets from theme: %s\", theme_dir)\n        utils.copy_media_files(\n            theme_dir, config['site_dir'], exclude=['*.py', '*.pyc', '*.html']\n        )\n\n    log.debug(\"Copying static assets from the docs dir.\")\n    utils.copy_media_files(config['docs_dir'], config['site_dir'])\n\n    log.debug(\"Building markdown pages.\")\n    build_pages(config)\n\n\ndef site_directory_contains_stale_files(site_directory):\n    \"\"\"\n    Check if the site directory contains stale files from a previous build.\n    Right now the check returns true if the directory is not empty.\n    A more sophisticated approach should be found to trigger only if there are\n    files that won't be overwritten anyway.\n    \"\"\"\n    if os.path.exists(site_directory):\n        if os.listdir(site_directory):\n            return True\n    return False\n", "path": "mkdocs/commands/build.py"}]}