Skip to content

Commit

Permalink
Merge pull request #4184 from stsewd/mkdocs-search-yaml
Browse files Browse the repository at this point in the history
Prepare code for custo mkdocs.yaml location
  • Loading branch information
ericholscher authored Jun 7, 2018
2 parents c1266fb + 8f3c966 commit 349963d
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 21 deletions.
4 changes: 1 addition & 3 deletions docs/builds.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,9 @@ MkDocs
~~~~~~

When you choose *Mkdocs* as your *Documentation Type*,
we will first look for a ``mkdocs.yml`` file in your repository.
we will first look for a ``mkdocs.yml`` file in the root of your repository.
If we don't find one,
we will generate one for you.
We will look inside a ``doc`` or ``docs`` directory first,
and then default to the top-level of your documentation.

Then MkDocs will build any files with a ``.md`` extension.
As MkDocs doesn't support automatic PDF generation,
Expand Down
53 changes: 38 additions & 15 deletions readthedocs/doc_builder/backends/mkdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@
.. _MkDocs: http://www.mkdocs.org/
"""
from __future__ import absolute_import
import os
import logging
from __future__ import (
absolute_import, division, print_function, unicode_literals)

import json
import yaml
import logging
import os

import yaml
from django.conf import settings
from django.template import loader as template_loader

Expand Down Expand Up @@ -55,6 +57,18 @@ def __init__(self, *args, **kwargs):
self.version.project.checkout_path(self.version.slug),
self.build_dir)
self.root_path = self.version.project.checkout_path(self.version.slug)
self.yaml_file = self.get_yaml_config()

def get_yaml_config(self):
"""Find the ``mkdocs.yml`` file in the project root."""
# TODO: try to load from the configuration file first.
test_path = os.path.join(
self.project.checkout_path(self.version.slug),
'mkdocs.yml'
)
if os.path.exists(test_path):
return test_path
return None

def load_yaml_config(self):
"""
Expand All @@ -64,7 +78,7 @@ def load_yaml_config(self):
"""
try:
return yaml.safe_load(
open(os.path.join(self.root_path, 'mkdocs.yml'), 'r')
open(self.yaml_file, 'r')
)
except IOError:
return {
Expand All @@ -76,13 +90,15 @@ def load_yaml_config(self):
mark = exc.problem_mark
note = ' (line %d, column %d)' % (mark.line + 1, mark.column + 1)
raise BuildEnvironmentError(
"Your mkdocs.yml could not be loaded, "
"possibly due to a syntax error%s" % (
note,))
'Your mkdocs.yml could not be loaded, '
'possibly due to a syntax error{note}'.format(note=note)
)

def append_conf(self, **__):
"""Set mkdocs config values."""
# Pull mkdocs config data
if not self.yaml_file:
self.yaml_file = os.path.join(self.root_path, 'mkdocs.yml')

user_config = self.load_yaml_config()

# Handle custom docs dirs
Expand All @@ -106,7 +122,10 @@ def append_conf(self, **__):
docs_path = os.path.join(self.root_path, docs_dir)

# RTD javascript writing
rtd_data = self.generate_rtd_data(docs_dir=docs_dir, mkdocs_config=user_config)
rtd_data = self.generate_rtd_data(
docs_dir=docs_dir,
mkdocs_config=user_config
)
with open(os.path.join(docs_path, 'readthedocs-data.js'), 'w') as f:
f.write(rtd_data)

Expand All @@ -115,25 +134,27 @@ def append_conf(self, **__):
user_config['google_analytics'] = None

# If using the readthedocs theme, apply the readthedocs.org overrides
# These use a global readthedocs search and customize the version selector
# These use a global readthedocs search
# and customize the version selector.
self.apply_theme_override(user_config)

# Write the modified mkdocs configuration
yaml.safe_dump(
user_config,
open(os.path.join(self.root_path, 'mkdocs.yml'), 'w')
open(self.yaml_file, 'w')
)

# Write the mkdocs.yml to the build logs
self.run(
'cat',
'mkdocs.yml',
os.path.relpath(self.yaml_file, self.root_path),
cwd=self.root_path,
)

def generate_rtd_data(self, docs_dir, mkdocs_config):
"""Generate template properties and render readthedocs-data.js."""
# Use the analytics code from mkdocs.yml if it isn't set already by Read the Docs
# Use the analytics code from mkdocs.yml
# if it isn't set already by Read the Docs,
analytics_code = self.version.project.analytics_code
if not analytics_code and mkdocs_config.get('google_analytics'):
# http://www.mkdocs.org/user-guide/configuration/#google_analytics
Expand Down Expand Up @@ -174,6 +195,7 @@ def build(self):
self.builder,
'--clean',
'--site-dir', self.build_dir,
'--config-file', self.yaml_file,
]
if self.use_theme:
build_command.extend(['--theme', 'readthedocs'])
Expand Down Expand Up @@ -222,7 +244,8 @@ def apply_theme_override(self, mkdocs_config):
:see: http://www.mkdocs.org/about/release-notes/#theme-customization-1164
"""
if self.get_theme_name(mkdocs_config) == self.READTHEDOCS_THEME_NAME:
# Overriding the theme is only necessary if the 'readthedocs' theme is used
# Overriding the theme is only necessary
# if the 'readthedocs' theme is used.
theme_setting = mkdocs_config.get('theme')
if isinstance(theme_setting, dict):
theme_setting['custom_dir'] = self.READTHEDOCS_TEMPLATE_OVERRIDE_DIR
Expand Down
216 changes: 213 additions & 3 deletions readthedocs/rtd_tests/tests/test_doc_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
from __future__ import (
absolute_import, division, print_function, unicode_literals)

from collections import namedtuple
import os
import tempfile
from collections import namedtuple

from django.test import TestCase
from mock import patch, Mock
import pytest
import yaml
import mock
from django.test import TestCase
from django_dynamic_fixture import get
from mock import patch

from readthedocs.builds.models import Version
from readthedocs.doc_builder.backends.mkdocs import BaseMkdocs, MkdocsHTML
from readthedocs.doc_builder.backends.sphinx import BaseSphinx, SearchBuilder
from readthedocs.projects.exceptions import ProjectConfigurationError
from readthedocs.projects.models import Project
Expand Down Expand Up @@ -108,3 +113,208 @@ def test_ignore_patterns(self):
# There is a dest/other/ but not a dest/_static
self.assertFalse(os.path.exists(dest_static))
self.assertTrue(os.path.exists(dest_other))


class MkdocsBuilderTest(TestCase):

def setUp(self):
self.project = get(Project, documentation_type='mkdocs', name='mkdocs')
self.version = get(Version, project=self.project)

self.build_env = namedtuple('project', 'version')
self.build_env.project = self.project
self.build_env.version = self.version

@patch('readthedocs.doc_builder.base.BaseBuilder.run')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_append_conf_create_yaml(self, checkout_path, run):
tmpdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tmpdir, 'docs'))
checkout_path.return_value = tmpdir

self.searchbuilder = MkdocsHTML(
build_env=self.build_env,
python_env=None
)
self.searchbuilder.append_conf()

run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY)

# There is a mkdocs.yml file created
generated_yaml = os.path.join(tmpdir, 'mkdocs.yml')
self.assertTrue(os.path.exists(generated_yaml))
config = yaml.safe_load(open(generated_yaml))
self.assertEqual(
config['docs_dir'],
os.path.join(tmpdir, 'docs')
)
self.assertEqual(
config['extra_css'],
[
'http://readthedocs.org/media/css/badge_only.css',
'http://readthedocs.org/media/css/readthedocs-doc-embed.css'
]
)
self.assertEqual(
config['extra_javascript'],
[
'readthedocs-data.js',
'http://readthedocs.org/media/static/core/js/readthedocs-doc-embed.js',
'http://readthedocs.org/media/javascript/readthedocs-analytics.js',
]
)
self.assertIsNone(
config['google_analytics'],
)
self.assertEqual(
config['site_name'],
'mkdocs'
)

@patch('readthedocs.doc_builder.base.BaseBuilder.run')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_append_conf_existing_yaml_on_root(self, checkout_path, run):
tmpdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tmpdir, 'docs'))
yaml_file = os.path.join(tmpdir, 'mkdocs.yml')
yaml.safe_dump(
{
'site_name': 'mkdocs',
'google_analytics': ['UA-1234-5', 'mkdocs.org'],
'docs_dir': 'docs',
},
open(yaml_file, 'w')
)
checkout_path.return_value = tmpdir

self.searchbuilder = MkdocsHTML(
build_env=self.build_env,
python_env=None
)
self.searchbuilder.append_conf()

run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY)

config = yaml.safe_load(open(yaml_file))
self.assertEqual(
config['docs_dir'],
'docs'
)
self.assertEqual(
config['extra_css'],
[
'http://readthedocs.org/media/css/badge_only.css',
'http://readthedocs.org/media/css/readthedocs-doc-embed.css'
]
)
self.assertEqual(
config['extra_javascript'],
[
'readthedocs-data.js',
'http://readthedocs.org/media/static/core/js/readthedocs-doc-embed.js',
'http://readthedocs.org/media/javascript/readthedocs-analytics.js',
]
)
self.assertIsNone(
config['google_analytics'],
)
self.assertEqual(
config['site_name'],
'mkdocs'
)

@patch('readthedocs.doc_builder.base.BaseBuilder.run')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_override_theme_new_style(self, checkout_path, run):
tmpdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tmpdir, 'docs'))
yaml_file = os.path.join(tmpdir, 'mkdocs.yml')
yaml.safe_dump(
{
'theme': {
'name': 'readthedocs',
},
'site_name': 'mkdocs',
'docs_dir': 'docs',
},
open(yaml_file, 'w')
)
checkout_path.return_value = tmpdir

self.searchbuilder = MkdocsHTML(
build_env=self.build_env,
python_env=None
)
self.searchbuilder.append_conf()

run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY)

config = yaml.safe_load(open(yaml_file))
self.assertEqual(
config['theme'],
{
'name': 'readthedocs',
'custom_dir': BaseMkdocs.READTHEDOCS_TEMPLATE_OVERRIDE_DIR
}
)

@patch('readthedocs.doc_builder.base.BaseBuilder.run')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_override_theme_old_style(self, checkout_path, run):
tmpdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tmpdir, 'docs'))
yaml_file = os.path.join(tmpdir, 'mkdocs.yml')
yaml.safe_dump(
{
'theme': 'readthedocs',
'site_name': 'mkdocs',
'docs_dir': 'docs',
},
open(yaml_file, 'w')
)
checkout_path.return_value = tmpdir

self.searchbuilder = MkdocsHTML(
build_env=self.build_env,
python_env=None
)
self.searchbuilder.append_conf()

run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY)

config = yaml.safe_load(open(yaml_file))
self.assertEqual(
config['theme_dir'],
BaseMkdocs.READTHEDOCS_TEMPLATE_OVERRIDE_DIR
)

@patch('readthedocs.doc_builder.base.BaseBuilder.run')
@patch('readthedocs.projects.models.Project.checkout_path')
def test_dont_override_theme(self, checkout_path, run):
tmpdir = tempfile.mkdtemp()
os.mkdir(os.path.join(tmpdir, 'docs'))
yaml_file = os.path.join(tmpdir, 'mkdocs.yml')
yaml.safe_dump(
{
'theme': 'not-readthedocs',
'theme_dir': 'not-readthedocs',
'site_name': 'mkdocs',
'docs_dir': 'docs',
},
open(yaml_file, 'w')
)
checkout_path.return_value = tmpdir

self.searchbuilder = MkdocsHTML(
build_env=self.build_env,
python_env=None
)
self.searchbuilder.append_conf()

run.assert_called_with('cat', 'mkdocs.yml', cwd=mock.ANY)

config = yaml.safe_load(open(yaml_file))
self.assertEqual(
config['theme_dir'],
'not-readthedocs'
)

0 comments on commit 349963d

Please sign in to comment.