diff --git a/.gitignore b/.gitignore index bcbc199..7408ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ ve/ *.py[co] *.egg *.egg-info +*.komodo* diff --git a/setup.py b/setup.py index 22837bc..6caa029 100644 --- a/setup.py +++ b/setup.py @@ -15,7 +15,10 @@ packages = find_packages('src'), package_dir = {'': 'src'}, - package_data={'pyll': ['templates/default.html'],}, + package_data={ + 'pyll': ['templates/default.html', + 'quickstart.tar.gz'], + }, zip_safe = False, test_suite = 'nose.collector', @@ -30,6 +33,8 @@ 'markdown', 'docutils', 'Jinja2', + 'python-dateutil==1.5', + 'PyYAML', ], ) diff --git a/src/pyll/app.py b/src/pyll/app.py index 5eea322..ef897a5 100644 --- a/src/pyll/app.py +++ b/src/pyll/app.py @@ -3,7 +3,7 @@ from datetime import datetime import imp import logging -import ConfigParser +import yaml from optparse import OptionParser from os import makedirs, getcwd, getlogin from os.path import splitext, join, dirname, split, abspath, getctime,\ @@ -126,8 +126,8 @@ def _parse(self, input_data): # parse the page parser_cls = parser.get_parser_for_filename(page['path']) - with open(path, 'r', encoding='utf8') as f: - parser_inst = parser_cls(self.settings, f.read()) + with open(path, 'r', encoding='utf-8') as f: + parser_inst = parser_cls(self.settings, f.read(), page['path']) try: parsed = parser_inst.parse() @@ -257,23 +257,23 @@ def run(self): def quickstart(settings): login = getlogin() - config = ConfigParser.SafeConfigParser() - config.add_section('pyll') author_name = raw_input("Author Name [%s]: " % login) or login - config.set('pyll', 'author_name', author_name) author_email_default = '%s@example.org' % login author_email = raw_input("Author Email [%s]: " % author_email_default) or author_email_default - config.set('pyll', 'author_email', author_email) website_url_default = 'http://www.example.org' website_url = raw_input("Website URL [%s]: " % website_url_default) or website_url_default - config.set('pyll', 'website_url', website_url) + config = {'pyll': { + 'author_name': author_name, + 'author_email': author_email, + 'website_url': website_url, + }} # before writing the settings file, make sure the _lib dir exists if not exists(settings['lib_dir']): makedirs(settings['lib_dir']) - with open(settings['settings_path'], 'wb') as configfile: - config.write(configfile) + with open(settings['settings_path'], 'wb', encoding='utf-8') as configfile: + configfile.write(yaml.dump(config, default_flow_style=False)) # extract template tmpl_path = join(dirname(abspath(__file__)), 'quickstart.tar.gz') @@ -282,7 +282,7 @@ def quickstart(settings): tar.extractall(path=settings['project_dir']) tar.close() - return dict(config.items('pyll')) + return config['pyll'] def main(): parser = OptionParser(version="%prog " + __version__) @@ -306,8 +306,7 @@ def main(): 'lib_dir': join(project_dir, '_lib'), 'url_path': join(project_dir, '_lib', 'urls.py'), 'settings_path': join(project_dir, '_lib', 'settings.cfg'), - 'build_time': datetime.today(), - 'date_format': '%Y-%m-%d'} + 'build_time': datetime.today()} # configure logging logging_level = LOGGING_LEVELS.get(options.logging, logging.INFO) @@ -322,9 +321,9 @@ def main(): # read settings file if exists(settings['settings_path']): - config = ConfigParser.SafeConfigParser() - config.read(settings['settings_path']) - settings.update(dict(config.items('pyll'))) + with open(settings['settings_path'], 'rb', encoding='utf-8') as configfile: + config = yaml.safe_load(configfile.read()) + settings.update(config['pyll']) logging.debug('settings %s', settings) # initialize site diff --git a/src/pyll/parser.py b/src/pyll/parser.py index 5e43a9d..e4f93f8 100644 --- a/src/pyll/parser.py +++ b/src/pyll/parser.py @@ -1,7 +1,8 @@ -import datetime -import re +import logging +import yaml from os.path import splitext + class ParserException(Exception): """Exception raised for errors during the parsing.""" pass @@ -9,77 +10,21 @@ class ParserException(Exception): class Parser(object): output_ext = None - def __init__(self, settings, source): + def __init__(self, settings, source, filename): self.settings = settings self.source = source self.headers = {} self.text = '' + self.filename = filename def _parse_headers(self): """ - Parses and removes the headers from the source. - """ - META_RE = re.compile( - r'^[ ]{0,3}(?P[A-Za-z0-9_-]+):\s*(?P.*)') - lines = self.source.splitlines() - for num, line in enumerate(lines): - match = META_RE.match(line) - if match: - key = match.group('key').strip().lower() - value = match.group('value').strip() - if value: - # custom header transformation - header_method = getattr(self, '_parse_%s_header' % key, - None) - if header_method: - value = header_method(value) - self.headers[key] = value - num_last_match = num - else: - break - # remove header lines from input source - try: - del lines[:num_last_match + 1] - except UnboundLocalError: - pass - - # check if a blank line followed the header lines and remove it - try: - if not lines[0]: - del lines[0] - except IndexError: - pass - self.text = '\n'.join(lines) - - def _parse_tags_header(self, value): - """ - Parses the value from the 'tags' header into a list. - """ - return [t.strip() for t in value.split(',')] - - def _parse_date_header(self, value): - """ - Parses the date header into a python datetime.datetime object. - The value must be in the format as specified by the 'date_format' - setting; otherwise a ParserException will be thrown. - """ - format = self.settings['date_format'] - try: - return datetime.datetime.strptime(value, format) - except ValueError as error: - raise ParserException(error) - - def _parse_updated_header(self, value): - return self._parse_date_header(value) - - def _parse_status_header(self, value): - """ - Checks that the value of the 'status' header is 'live', 'hidden' or - 'draft'. If not 'live' is returned. + Parses the headers from the source. """ - if value in ('live', 'hidden', 'draft'): - return value - return 'live' + self.headers = yaml.safe_load(self.header_raw) if self.header_raw != '' else {} + if not 'status' in self.headers \ + or self.headers['status'] not in ('live', 'hidden', 'draft'): + self.headers['status'] = 'live' def _parse_text(self): """ @@ -88,7 +33,24 @@ def _parse_text(self): """ return self.text + def _split_input(self): + """ + Segregates header from actual text. + Used to later parse these parts a different way. + """ + parts = self.source.split("\n\n", 1) + if len(parts) < 2: + parts = self.source.split("---\n", 1) + if len(parts) >= 2: + self.header_raw = parts[0] + self.text = parts[-1] + else: + logging.warn("'%s' has no headers, only content - at least 'title' will be missing.", self.filename) + self.header_raw = '' + self.text = self.source + def parse(self): + self._split_input() self._parse_headers() self._parse_text() return (self.headers, self.text) diff --git a/src/pyll/quickstart.tar.gz b/src/pyll/quickstart.tar.gz index 5e867e2..78341cd 100644 Binary files a/src/pyll/quickstart.tar.gz and b/src/pyll/quickstart.tar.gz differ