Repository: getpelican/pelican

Issue:
Infinite loop on `make serve`
Just installed a copy of pelican from the repo (master branch) into a fresh virtualenv.

Ran the `pelican-quickstart` script and then `make html` and it worked fine.

However whenever I try to `make serve`, initially it was complaining about permissions for `develop_server.sh` which was not allowed to be executed. So I did `chmod +x develop_server.sh` which solved that problem however now whenever I run the `serve`, it gets into infinite loop with this message:

`WARNING: Caught exception "max() arg is an empty sequence". Reloading.`

And while it keeps printing that line, I can't figure out how to quit the process. I tried `Ctrl-C` and it does not help. So the only way I figured is to actually close the terminal window.



Discussion:
Running ./develop_server.sh stop would fix that, even though text is scrolling you are at a prompt.

I think #443 and 451 are the same ticket

What I am saying is that make serve just runs pelican -r and python SimpleHTTPServer...
these are the same issue if this is just referring to the scroll

Sorry for late reply. Just saw the responses. @justinmayer I did make it executable.

@miki725 I think perhaps you're misunderstanding how GitHub works here. That's a commit message, in which I fixed the problem that required you to do that. It shouldn't be necessary to manually `chmod +x` that file in the future.

I'll defer to @tbunnyman regarding the "infinite loop" topic.

This problem is when a directory is empty. So if you do the quickstart, and then do not write any posts, the content directory will be empty. In [pelican.utils.files_changed](https://github.com/getpelican/pelican/blob/master/pelican/utils.py#L232), there is a test to get the max mtime of any of the files in the directory. However, if there are no files in the directory, [the call to max](https://github.com/getpelican/pelican/blob/master/pelican/utils.py#L244) raises an Exception with the message `max() arg is an empty sequence`.

It seems like the fix would be to catch the exception and return False in `files_changed`.

@streeter +1, this jives with behavior I'm seeing — the infinite loop when I haven't yet written any posts. (which is actually according to the "kickstart a blog" section of the [getting started](http://docs.getpelican.com/en/latest/getting_started.html) doc)

@case: The docs do indicate that content is needed (emphasis added):

> Once you finish answering all the questions, you can begin adding content to the content folder that has been created for you. [...] **Once you have some content to generate**, you can convert it to HTML via the following command:

That said, we can probably improve things further. I think the most expedient solution is to have `develop_server.sh` test for the presence of content, print a helpful error message indicating the lack of content, and exit gracefully.


Resolving patch:
diff --git a/pelican/__init__.py b/pelican/__init__.py
--- a/pelican/__init__.py
+++ b/pelican/__init__.py
@@ -11,7 +11,7 @@
         StaticGenerator, PdfGenerator, LessCSSGenerator)
 from pelican.log import init
 from pelican.settings import read_settings, _DEFAULT_CONFIG
-from pelican.utils import clean_output_dir, files_changed, file_changed
+from pelican.utils import clean_output_dir, files_changed, file_changed, NoFilesError
 from pelican.writers import Writer
 
 __major__ = 3
@@ -265,6 +265,7 @@ def main():
 
     try:
         if args.autoreload:
+            files_found_error = True
             while True:
                 try:
                     # Check source dir for changed files ending with the given
@@ -274,6 +275,8 @@ def main():
                     # have.
                     if files_changed(pelican.path, pelican.markup) or \
                             files_changed(pelican.theme, ['']):
+                        if files_found_error == False:
+                            files_found_error = True
                         pelican.run()
 
                     # reload also if settings.py changed
@@ -287,6 +290,10 @@ def main():
                 except KeyboardInterrupt:
                     logger.warning("Keyboard interrupt, quitting.")
                     break
+                except NoFilesError:
+                    if files_found_error == True:
+                        logger.warning("No valid files found in content. Nothing to generate.")
+                        files_found_error = False
                 except Exception, e:
                     logger.warning(
                         "Caught exception \"{}\". Reloading.".format(e)
diff --git a/pelican/utils.py b/pelican/utils.py
--- a/pelican/utils.py
+++ b/pelican/utils.py
@@ -14,6 +14,9 @@
 
 logger = logging.getLogger(__name__)
 
+class NoFilesError(Exception):
+    pass
+
 
 def get_date(string):
     """Return a datetime object from a string.
@@ -241,10 +244,13 @@ def file_times(path):
                     yield os.stat(os.path.join(root, f)).st_mtime
 
     global LAST_MTIME
-    mtime = max(file_times(path))
-    if mtime > LAST_MTIME:
-        LAST_MTIME = mtime
-        return True
+    try:
+        mtime = max(file_times(path))
+        if mtime > LAST_MTIME:
+            LAST_MTIME = mtime
+            return True
+    except ValueError:
+        raise NoFilesError("No files with the given extension(s) found.")
     return False
 
 


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -6,6 +6,7 @@
 
 from pelican import utils
 from .support import get_article, unittest
+from pelican.utils import NoFilesError
 
 
 class TestUtils(unittest.TestCase):
@@ -74,7 +75,8 @@ def test_process_translations(self):
         self.assertNotIn(fr_article1, index)
 
     def test_files_changed(self):
-        "Test if file changes are correctly detected"
+        """Test if file changes are correctly detected
+        Make sure to handle not getting any files correctly"""
 
         path = os.path.join(os.path.dirname(__file__), 'content')
         filename = os.path.join(path, 'article_with_metadata.rst')
@@ -90,6 +92,18 @@ def test_files_changed(self):
         self.assertEquals(changed, True)
         self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1)
 
+        empty_path = os.path.join(os.path.dirname(__file__), 'empty')
+        try:
+            os.mkdir(empty_path)
+            os.mkdir(os.path.join(empty_path, "empty_folder"))
+            shutil.copy(__file__, empty_path)
+            with self.assertRaises(NoFilesError):
+                utils.files_changed(empty_path, 'rst')
+        except OSError:
+            self.fail("OSError Exception in test_files_changed test")
+        finally:
+            shutil.rmtree(empty_path, True)
+
     def test_clean_output_dir(self):
         test_directory = os.path.join(os.path.dirname(__file__), 'clean_output')
         content = os.path.join(os.path.dirname(__file__), 'content')