Repository: mkdocs/mkdocs

Issue:
Ignore broken symlinks in mkdocs serve.
What I am experiencing:
- When I am editing `index.md` in Emacs, Emacs creates files like:

```
➜  docs git:(master) ✗ ls -al .#*  
lrwxrwxrwx 1 paulproteus paulproteus 36 Jun 17 17:24 .#index.md -> paulproteus@charkha.27783:1434311808
```
- These files are Emacs' way of using symlinks to track which computer+process was Emacs-ing the file, so that in case of a crash, Emacs can figure out how to restore its state.

What I expect:
- When I edit `somethingelse.md` and press save, I expect the mkdocs livereload to reload the browser.

What I see instead:

```
INFO    -  Building documentation... 
ERROR   -  file not found: /home/paulproteus/projects/sandstorm/docs/.#index.md 
ERROR   -  Error building page .#index.md 
[E 150617 17:22:21 ioloop:612] Exception in callback (3, <function null_wrapper at 0x7fc883190500>)
    Traceback (most recent call last):
      File "/home/paulproteus/.local/lib/python2.7/site-packages/tornado/ioloop.py", line 866, in start
        handler_func(fd_obj, events)
      File "/home/paulproteus/.local/lib/python2.7/site-packages/tornado/stack_context.py", line 275, in null_wrapper
        return fn(*args, **kwargs)
      File "/usr/lib/python2.7/dist-packages/pyinotify.py", line 1604, in handle_read
        self.process_events()
      File "/usr/lib/python2.7/dist-packages/pyinotify.py", line 1321, in process_events
        self._default_proc_fun(revent)
      File "/home/paulproteus/.local/lib/python2.7/site-packages/livereload/watcher.py", line 152, in inotify_event
        self.callback()
      File "/home/paulproteus/.local/lib/python2.7/site-packages/livereload/handlers.py", line 65, in poll_tasks
        filepath, delay = cls.watcher.examine()
      File "/home/paulproteus/.local/lib/python2.7/site-packages/livereload/watcher.py", line 72, in examine
        func and func()
      File "/usr/lib/python2.7/dist-packages/mkdocs/serve.py", line 74, in builder
        build(config, live_server=True)
      File "/usr/lib/python2.7/dist-packages/mkdocs/build.py", line 299, in build
        build_pages(config)
      File "/usr/lib/python2.7/dist-packages/mkdocs/build.py", line 259, in build_pages
        dump_json)
      File "/usr/lib/python2.7/dist-packages/mkdocs/build.py", line 171, in _build_page
        input_content = io.open(input_path, 'r', encoding='utf-8').read()
    IOError: [Errno 2] No such file or directory: '/home/paulproteus/projects/sandstorm/docs/.#index.md'
```

What I propose:
- If a "No such file or directory" error occurs, but the problem is a broken symlink, the `mkdocs` build should continue as if the file does not exist. Note that this arguably is a special-case to handle Emacs' own weirdness; a different way to do it would be to look at the list of git ignored files.
- Perhaps in general, `mkdocs` should issue a warning (not an error) on broken symlinks, gracefully ignoring them.

I'm open to a bunch of ideas. I wanted to file this in the hopes of sparking a discussion where the maintainers of mkdocs could express their opinion about the best way forward.

Thanks so much! Also hi! I'm twitter.com/asheeshlaroia and was chatting with a mkdocs developer earlier today. Seems like a great project! I learned about it via http://ericholscher.com/blog/2014/feb/27/how-i-judge-documentation-quality/ !



Discussion:
Interesting, I think we will want to add an extra check in the [file discovery](https://github.com/mkdocs/mkdocs/blob/master/mkdocs/config/config_options.py#L326-L336) to check is the file is a symlink and then check that the target is valid. If it isn't we can ignore it. We can probably do this with a combination of `os.path.islink` and `os.readlink`.


Resolving patch:
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -332,6 +332,13 @@ def walk_docs_dir(self, docs_dir):
             dirs.sort()
             for filename in sorted(filenames):
                 fullpath = os.path.join(dirpath, filename)
+
+                # Some editors (namely Emacs) will create temporary symlinks
+                # for internal magic. We can just ignore these files.
+                if os.path.islink(fullpath):
+                    if not os.path.exists(os.readlink(fullpath)):
+                        continue
+
                 relpath = os.path.normpath(os.path.relpath(fullpath, docs_dir))
                 if self.file_match(relpath):
                     yield relpath


Test patch:
diff --git a/mkdocs/tests/config/config_options_tests.py b/mkdocs/tests/config/config_options_tests.py
--- a/mkdocs/tests/config/config_options_tests.py
+++ b/mkdocs/tests/config/config_options_tests.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import os
+import tempfile
 import unittest
 
 from mkdocs import utils
@@ -251,6 +252,25 @@ def test_invalid(self):
         self.assertRaises(config_options.ValidationError,
                           option.validate, {})
 
+    def test_talk(self):
+
+        option = config_options.Extras(utils.is_markdown_file)
+
+        tmp_dir = tempfile.mkdtemp()
+
+        f1 = os.path.join(tmp_dir, 'file1.md')
+        f2 = os.path.join(tmp_dir, 'file2.md')
+
+        open(f1, 'a').close()
+
+        # symlink isn't available on Python 2 on Windows.
+        if hasattr(os, 'symlink'):
+            os.symlink('/path/that/doesnt/exist', f2)
+
+        files = list(option.walk_docs_dir(tmp_dir))
+
+        self.assertEqual(['file1.md', ], files)
+
 
 class PagesTest(unittest.TestCase):