1
0
Fork 0
forked from github/pelican

Merge pull request #857 from avaris/feedback

add feedback to user and refactor 'autoreload' code
This commit is contained in:
Justin Mayer 2013-04-20 07:57:56 -07:00
commit e55878fc2e
4 changed files with 106 additions and 78 deletions

View file

@ -17,8 +17,7 @@ from pelican.generators import (ArticlesGenerator, PagesGenerator,
SourceFileGenerator, TemplatePagesGenerator) SourceFileGenerator, TemplatePagesGenerator)
from pelican.log import init from pelican.log import init
from pelican.settings import read_settings from pelican.settings import read_settings
from pelican.utils import (clean_output_dir, files_changed, file_changed, from pelican.utils import clean_output_dir, folder_watcher, file_watcher
NoFilesError)
from pelican.writers import Writer from pelican.writers import Writer
__major__ = 3 __major__ = 3
@ -149,6 +148,7 @@ class Pelican(object):
def run(self): def run(self):
"""Run the generators and return""" """Run the generators and return"""
start_time = time.time()
context = self.settings.copy() context = self.settings.copy()
context['filenames'] = {} # share the dict between all the generators context['filenames'] = {} # share the dict between all the generators
@ -182,6 +182,14 @@ class Pelican(object):
signals.finalized.send(self) signals.finalized.send(self)
articles_generator = next(g for g in generators if isinstance(g, ArticlesGenerator))
pages_generator = next(g for g in generators if isinstance(g, PagesGenerator))
print('Done: Processed {} articles and {} pages in {:.2f} seconds.'.format(
len(articles_generator.articles) + len(articles_generator.translations),
len(pages_generator.pages) + len(pages_generator.translations),
time.time() - start_time))
def get_generator_classes(self): def get_generator_classes(self):
generators = [StaticGenerator, ArticlesGenerator, PagesGenerator] generators = [StaticGenerator, ArticlesGenerator, PagesGenerator]
@ -308,9 +316,19 @@ def main():
init(args.verbosity) init(args.verbosity)
pelican = get_instance(args) pelican = get_instance(args)
watchers = {'content': folder_watcher(pelican.path,
pelican.markup,
pelican.ignore_files),
'theme': folder_watcher(pelican.theme,
[''],
pelican.ignore_files),
'settings': file_watcher(args.settings)}
try: try:
if args.autoreload: if args.autoreload:
files_found_error = True print(' --- AutoReload Mode: Monitoring `content`, `theme` and `settings`'
' for changes. ---')
while True: while True:
try: try:
# Check source dir for changed files ending with the given # Check source dir for changed files ending with the given
@ -318,45 +336,45 @@ def main():
# restriction; all files are recursively checked if they # restriction; all files are recursively checked if they
# have changed, no matter what extension the filenames # have changed, no matter what extension the filenames
# have. # have.
if (files_changed( modified = {k: next(v) for k, v in watchers.items()}
pelican.path,
pelican.markup,
pelican.ignore_files)
or files_changed(
pelican.theme,
[''],
pelican.ignore_files
)):
if not files_found_error:
files_found_error = True
pelican.run()
# reload also if settings.py changed if modified['settings']:
if file_changed(args.settings):
logger.info('%s changed, re-generating' %
args.settings)
pelican = get_instance(args) pelican = get_instance(args)
if any(modified.values()):
print('\n-> Modified: {}. re-generating...'.format(
', '.join(k for k, v in modified.items() if v)))
if modified['content'] is None:
logger.warning('No valid files found in content.')
if modified['theme'] is None:
logger.warning('Empty theme folder. Using `basic` theme.')
pelican.run() pelican.run()
time.sleep(.5) # sleep to avoid cpu load time.sleep(.5) # sleep to avoid cpu load
except KeyboardInterrupt: except KeyboardInterrupt:
logger.warning("Keyboard interrupt, quitting.") logger.warning("Keyboard interrupt, quitting.")
break break
except NoFilesError:
if files_found_error:
logger.warning("No valid files found in content. "
"Nothing to generate.")
files_found_error = False
time.sleep(1) # sleep to avoid cpu load
except Exception as e: except Exception as e:
if (args.verbosity == logging.DEBUG): if (args.verbosity == logging.DEBUG):
logger.critical(e.args) logger.critical(e.args)
raise raise
logger.warning( logger.warning(
'Caught exception "{0}". Reloading.'.format(e)) 'Caught exception "{0}". Reloading.'.format(e))
continue
else: else:
if next(watchers['content']) is None:
logger.warning('No valid files found in content.')
if next(watchers['theme']) is None:
logger.warning('Empty theme folder. Using `basic` theme.')
pelican.run() pelican.run()
except Exception as e: except Exception as e:
logger.critical(e) logger.critical(e)

View file

@ -10,7 +10,7 @@ import logging
from pelican import Pelican from pelican import Pelican
from pelican.settings import read_settings from pelican.settings import read_settings
from pelican.tests.support import LoggedTestCase from pelican.tests.support import LoggedTestCase, mute
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
SAMPLES_PATH = os.path.abspath(os.path.join( SAMPLES_PATH = os.path.abspath(os.path.join(
@ -73,7 +73,7 @@ class TestPelican(LoggedTestCase):
'LOCALE': locale.normalize('en_US'), 'LOCALE': locale.normalize('en_US'),
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
pelican.run() mute(True)(pelican.run)()
dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'basic')) dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
self.assertFilesEqual(recursiveDiff(dcmp)) self.assertFilesEqual(recursiveDiff(dcmp))
self.assertLogCountEqual( self.assertLogCountEqual(
@ -89,6 +89,6 @@ class TestPelican(LoggedTestCase):
'LOCALE': locale.normalize('en_US'), 'LOCALE': locale.normalize('en_US'),
}) })
pelican = Pelican(settings=settings) pelican = Pelican(settings=settings)
pelican.run() mute(True)(pelican.run)()
dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'custom')) dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
self.assertFilesEqual(recursiveDiff(dcmp)) self.assertFilesEqual(recursiveDiff(dcmp))

View file

@ -10,7 +10,6 @@ from sys import platform
from pelican import utils from pelican import utils
from .support import get_article, LoggedTestCase, locale_available, unittest from .support import get_article, LoggedTestCase, locale_available, unittest
from pelican.utils import NoFilesError
class TestUtils(LoggedTestCase): class TestUtils(LoggedTestCase):
@ -127,31 +126,43 @@ class TestUtils(LoggedTestCase):
self.assertNotIn(en_article3, trans) self.assertNotIn(en_article3, trans)
self.assertNotIn(fr_article3, index) self.assertNotIn(fr_article3, index)
def test_files_changed(self): def test_watchers(self):
# Test if file changes are correctly detected # Test if file changes are correctly detected
# Make sure to handle not getting any files correctly. # Make sure to handle not getting any files correctly.
dirname = os.path.join(os.path.dirname(__file__), 'content') dirname = os.path.join(os.path.dirname(__file__), 'content')
folder_watcher = utils.folder_watcher(dirname, ['rst'])
path = os.path.join(dirname, 'article_with_metadata.rst') path = os.path.join(dirname, 'article_with_metadata.rst')
changed = utils.files_changed(dirname, 'rst') file_watcher = utils.file_watcher(path)
self.assertEqual(changed, True)
changed = utils.files_changed(dirname, 'rst') # first check returns True
self.assertEqual(changed, False) self.assertEqual(next(folder_watcher), True)
self.assertEqual(next(file_watcher), True)
# next check without modification returns False
self.assertEqual(next(folder_watcher), False)
self.assertEqual(next(file_watcher), False)
# after modification, returns True
t = time.time() t = time.time()
os.utime(path, (t, t)) os.utime(path, (t, t))
changed = utils.files_changed(dirname, 'rst') self.assertEqual(next(folder_watcher), True)
self.assertEqual(changed, True) self.assertEqual(next(file_watcher), True)
self.assertAlmostEqual(utils.LAST_MTIME, t, delta=1)
# file watcher with None or empty path should return None
self.assertEqual(next(utils.file_watcher('')), None)
self.assertEqual(next(utils.file_watcher(None)), None)
empty_path = os.path.join(os.path.dirname(__file__), 'empty') empty_path = os.path.join(os.path.dirname(__file__), 'empty')
try: try:
os.mkdir(empty_path) os.mkdir(empty_path)
os.mkdir(os.path.join(empty_path, "empty_folder")) os.mkdir(os.path.join(empty_path, "empty_folder"))
shutil.copy(__file__, empty_path) shutil.copy(__file__, empty_path)
with self.assertRaises(NoFilesError):
utils.files_changed(empty_path, 'rst') # if no files of interest, returns None
watcher = utils.folder_watcher(empty_path, ['rst'])
self.assertEqual(next(watcher), None)
except OSError: except OSError:
self.fail("OSError Exception in test_files_changed test") self.fail("OSError Exception in test_files_changed test")
finally: finally:

View file

@ -11,7 +11,7 @@ import logging
import errno import errno
import locale import locale
import fnmatch import fnmatch
from collections import defaultdict, Hashable from collections import Hashable
from functools import partial from functools import partial
from codecs import open, BOM_UTF8 from codecs import open, BOM_UTF8
@ -81,10 +81,6 @@ def python_2_unicode_compatible(klass):
return klass return klass
class NoFilesError(Exception):
pass
class memoized(object): class memoized(object):
"""Function decorator to cache return values. """Function decorator to cache return values.
@ -433,46 +429,49 @@ def process_translations(content_list):
return index, translations return index, translations
LAST_MTIME = 0 def folder_watcher(path, extensions, ignores=[]):
'''Generator for monitoring a folder for modifications.
Returns a boolean indicating if files are changed since last check.
def files_changed(path, extensions, ignores=[]): Returns None if there are no matching files in the folder'''
"""Return True if the files have changed since the last check"""
def file_times(path): def file_times(path):
"""Return the last time files have been modified""" '''Return `mtime` for each file in path'''
for root, dirs, files in os.walk(path): for root, dirs, files in os.walk(path):
dirs[:] = [x for x in dirs if x[0] != os.curdir] dirs[:] = [x for x in dirs if not x.startswith(os.curdir)]
for f in files: for f in files:
if any(f.endswith(ext) for ext in extensions) \ if (f.endswith(tuple(extensions)) and
and not any(fnmatch.fnmatch(f, ignore) not any(fnmatch.fnmatch(f, ignore) for ignore in ignores)):
for ignore in ignores):
yield os.stat(os.path.join(root, f)).st_mtime yield os.stat(os.path.join(root, f)).st_mtime
global LAST_MTIME LAST_MTIME = 0
while True:
try: try:
mtime = max(file_times(path)) mtime = max(file_times(path))
if mtime > LAST_MTIME: if mtime > LAST_MTIME:
LAST_MTIME = mtime LAST_MTIME = mtime
return True yield True
except ValueError: except ValueError:
raise NoFilesError("No files with the given extension(s) found.") yield None
return False
FILENAMES_MTIMES = defaultdict(int)
def file_changed(path):
mtime = os.stat(path).st_mtime
if FILENAMES_MTIMES[path] == 0:
FILENAMES_MTIMES[path] = mtime
return False
else: else:
if mtime > FILENAMES_MTIMES[path]: yield False
FILENAMES_MTIMES[path] = mtime
return True
return False def file_watcher(path):
'''Generator for monitoring a file for modifications'''
LAST_MTIME = 0
while True:
if path:
mtime = os.stat(path).st_mtime
if mtime > LAST_MTIME:
LAST_MTIME = mtime
yield True
else:
yield False
else:
yield None
def set_date_tzinfo(d, tz_name=None): def set_date_tzinfo(d, tz_name=None):